π§ͺ Project: Test Suite Builder
π Lesson 10: Testing Your Code
β± 30-35 min
π Intermediate
π― Objective
Write a comprehensive test suite for a utility library. Practice organizing tests with groups, using various matchers, testing edge cases, and achieving high code coverage.
Requirements
- Create a
StringUtilsclass with methods to test - Write tests organized in groups and subgroups
- Use a variety of matchers (equals, contains, throwsException, etc.)
- Test edge cases: empty strings, null values, special characters
- Write async tests for simulated operations
- Use
setUpandtearDownfor test preparation
// ============================================
// FILE: lib/string_utils.dart (Code to test)
// ============================================
class StringUtils {
static String capitalize(String input) {
if (input.isEmpty) return input;
return '${input[0].toUpperCase()}${input.substring(1).toLowerCase()}';
}
static String reverse(String input) {
return input.split('').reversed.join();
}
static int countWords(String input) {
if (input.trim().isEmpty) return 0;
return input.trim().split(RegExp(r'\s+')).length;
}
static bool isPalindrome(String input) {
final cleaned = input.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]'), '');
return cleaned == cleaned.split('').reversed.join();
}
static String truncate(String input, int maxLength) {
if (input.length <= maxLength) return input;
return '${input.substring(0, maxLength)}...';
}
static String? validateEmail(String? email) {
if (email == null || email.isEmpty) return 'Email is required';
final regex = RegExp(r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$');
if (!regex.hasMatch(email)) return 'Invalid email format';
return null;
}
static Future processAsync(String input) async {
await Future.delayed(Duration(milliseconds: 10));
return input.toUpperCase();
}
}
// ============================================
// FILE: test/string_utils_test.dart (Tests)
// ============================================
import 'package:test/test.dart';
import '../lib/string_utils.dart';
void main() {
// setUp runs before each test
late String testString;
setUp(() {
testString = 'hello world';
});
group('StringUtils', () {
group('capitalize()', () {
test('capitalizes first letter and lowercases rest', () {
expect(StringUtils.capitalize('hello'), equals('Hello'));
expect(StringUtils.capitalize('HELLO'), equals('Hello'));
expect(StringUtils.capitalize('hElLo'), equals('Hello'));
});
test('handles empty string', () {
expect(StringUtils.capitalize(''), equals(''));
});
test('handles single character', () {
expect(StringUtils.capitalize('a'), equals('A'));
expect(StringUtils.capitalize('Z'), equals('Z'));
});
test('handles special characters', () {
expect(StringUtils.capitalize('123abc'), equals('123abc'));
expect(StringUtils.capitalize('!hello'), equals('!hello'));
});
});
group('reverse()', () {
test('reverses a string correctly', () {
expect(StringUtils.reverse('hello'), equals('olleh'));
expect(StringUtils.reverse('Dart'), equals('traD'));
});
test('palindrome check with reverse', () {
final palindrome = 'radar';
expect(StringUtils.reverse(palindrome), equals(palindrome));
});
test('handles empty string', () {
expect(StringUtils.reverse(''), equals(''));
});
});
group('countWords()', () {
test('counts words correctly', () {
expect(StringUtils.countWords('hello world'), equals(2));
expect(StringUtils.countWords('one two three'), equals(3));
expect(StringUtils.countWords('single'), equals(1));
});
test('handles multiple spaces', () {
expect(StringUtils.countWords('hello world'), equals(2));
expect(StringUtils.countWords(' spaced '), equals(1));
});
test('handles empty and whitespace strings', () {
expect(StringUtils.countWords(''), equals(0));
expect(StringUtils.countWords(' '), equals(0));
});
});
group('isPalindrome()', () {
test('identifies palindromes', () {
expect(StringUtils.isPalindrome('radar'), isTrue);
expect(StringUtils.isPalindrome('A man a plan a canal Panama'), isTrue);
expect(StringUtils.isPalindrome('race a car'), isFalse);
});
test('ignores case and non-alphanumeric', () {
expect(StringUtils.isPalindrome('RaceCar'), isTrue);
expect(StringUtils.isPalindrome('Madam, I\'m Adam'), isTrue);
});
});
group('truncate()', () {
test('truncates long strings', () {
expect(StringUtils.truncate('Hello World', 5), equals('Hello...'));
expect(StringUtils.truncate('Hi', 5), equals('Hi'));
});
test('handles exact length', () {
expect(StringUtils.truncate('Hello', 5), equals('Hello'));
});
});
group('validateEmail()', () {
test('validates correct emails', () {
expect(StringUtils.validateEmail('user@example.com'), isNull);
expect(StringUtils.validateEmail('test.user@domain.co'), isNull);
});
test('rejects invalid emails', () {
expect(StringUtils.validateEmail('invalid'), isNotNull);
expect(StringUtils.validateEmail('@nodomain.com'), isNotNull);
expect(StringUtils.validateEmail(''), isNotNull);
expect(StringUtils.validateEmail(null), isNotNull);
});
});
group('processAsync()', () {
test('processes string asynchronously', () async {
final result = await StringUtils.processAsync('hello');
expect(result, equals('HELLO'));
});
test('completes with correct value using completion matcher', () {
expect(
StringUtils.processAsync('dart'),
completion(equals('DART')),
);
});
});
group('edge cases', () {
test('handles very long strings', () {
final longString = 'a' * 10000;
expect(StringUtils.countWords(longString), equals(1));
});
test('handles unicode characters', () {
expect(StringUtils.capitalize('ΓΌber'), equals('Γber'));
expect(StringUtils.reverse('cafΓ©'), equals('Γ©fac'));
});
});
});
}