formix_test 0.1.0
formix_test: ^0.1.0 copied to clipboard
Testing utilities for the Formix ecosystem. Includes custom matchers, mock validators, and test helpers for validation testing.
formix_test #
Testing utilities for the Formix validation ecosystem. Includes custom matchers, mock validators, and test helpers.
Installation #
dev_dependencies:
formix_test: ^0.1.0
test: ^1.24.0
Features #
- Custom Matchers: Expressive matchers for validation results
- Mock Validators: Easily mock validators for testing
- Test Helpers: Utilities to run validators against multiple test cases
- Generators: Random data generators for fuzz testing
Matchers #
isValid() #
Matches a valid ValidationResult:
import 'package:formix_test/formix_test.dart';
test('email validator accepts valid emails', () {
expect(emailValidator.validate('test@example.com'), isValid());
});
isInvalid() #
Matches an invalid ValidationResult:
test('email validator rejects invalid emails', () {
expect(emailValidator.validate('invalid'), isInvalid());
});
hasError(error) #
Matches a result with a specific error:
test('shows correct error message', () {
expect(
emailValidator.validate(''),
hasError('Email is required'),
);
});
hasErrorWhere(predicate) #
Matches a result with an error matching a predicate:
test('error contains email', () {
expect(
emailValidator.validate('invalid'),
hasErrorWhere<String>((e) => e.toLowerCase().contains('email')),
);
});
hasErrorCount(count) #
Matches a result with exactly N errors:
test('collects all password errors', () {
final result = passwordValidator.validate('a'); // Too short, no uppercase, no digit
expect(result, hasErrorCount(3));
});
hasValidValue(value) #
Matches a valid result with specific value:
test('returns the validated value', () {
expect(
usernameValidator.validate('john_doe'),
hasValidValue('john_doe'),
);
});
Mock Validators #
MockValidator #
Create a validator with predefined responses:
import 'package:formix_test/formix_test.dart';
test('form uses email validator', () {
final mockEmail = MockValidator<String, String>()
..whenValid(['test@example.com', 'user@domain.org'])
..whenInvalid('', error: 'Required')
..whenInvalid('invalid', error: 'Invalid email')
..withDefaultError('Unknown error');
expect(mockEmail.validate('test@example.com'), isValid());
expect(mockEmail.validate(''), hasError('Required'));
expect(mockEmail.validate('invalid'), hasError('Invalid email'));
expect(mockEmail.validate('random'), hasError('Unknown error'));
});
RecordingValidator #
Track all validation calls:
test('validates on every keystroke', () {
final recorder = RecordingValidator<String, String>(
innerValidator: emailValidator,
);
recorder.validate('t');
recorder.validate('te');
recorder.validate('tes');
expect(recorder.callCount, 3);
expect(recorder.calls, ['t', 'te', 'tes']);
expect(recorder.lastValue, 'tes');
});
AlwaysValidValidator / AlwaysInvalidValidator #
Simple validators for testing:
test('form submits when all fields valid', () {
final form = MyForm(
emailValidator: AlwaysValidValidator(),
passwordValidator: AlwaysValidValidator(),
);
expect(form.isValid, isTrue);
});
test('form shows errors when fields invalid', () {
final form = MyForm(
emailValidator: AlwaysInvalidValidator('Error'),
passwordValidator: AlwaysValidValidator(),
);
expect(form.hasErrors, isTrue);
});
Test Helpers #
testValidator() #
Run a validator against multiple test cases at once:
import 'package:formix_test/formix_test.dart';
test('email validator', () {
testValidator(
emailValidator,
validCases: [
'test@example.com',
'user@domain.org',
'name+tag@company.co.uk',
],
invalidCases: [
'',
'invalid',
'@missing.user',
'no-at-sign.com',
'spaces in@email.com',
],
);
});
testValidator with callbacks #
test('password validator with detailed checks', () {
testValidator(
passwordValidator,
validCases: ['StrongP@ss1'],
invalidCases: ['weak', '12345678', 'ALLCAPS123'],
onValid: (value, result) {
print('$value passed validation');
},
onInvalid: (value, result) {
print('$value failed with: ${result.errors}');
},
);
});
Generators #
StringGenerator #
Generate random strings for fuzz testing:
import 'package:formix_test/formix_test.dart';
test('handles random input without crashing', () {
final generator = StringGenerator(42); // Optional seed for reproducibility
for (var i = 0; i < 100; i++) {
final random = generator.generate(20);
expect(() => validator.validate(random), returnsNormally);
}
});
test('email validator rejects random strings', () {
final generator = StringGenerator();
for (var i = 0; i < 50; i++) {
final random = generator.generate(10);
// Most random strings should not be valid emails
expect(emailValidator.validate(random), isInvalid());
}
});
Email Generation #
test('email validator accepts generated emails', () {
final generator = StringGenerator();
for (var i = 0; i < 10; i++) {
final email = generator.generateEmail();
expect(emailValidator.validate(email), isValid());
}
});
test('email validator rejects invalid emails', () {
final generator = StringGenerator();
for (var i = 0; i < 10; i++) {
final invalid = generator.generateInvalidEmail();
expect(emailValidator.validate(invalid), isInvalid());
}
});
Complete Test Example #
import 'package:formix_core/formix_core.dart';
import 'package:formix_validators/formix_validators.dart';
import 'package:formix_test/formix_test.dart';
import 'package:test/test.dart';
void main() {
group('UserRegistration', () {
late Formix<String, String> usernameValidator;
late Formix<String, String> emailValidator;
late Formix<String, String> passwordValidator;
setUp(() {
usernameValidator = Validate.all<String, String>([
StringRules.required(error: 'Username required'),
StringRules.minLength(3, error: 'Too short'),
StringRules.maxLength(20, error: 'Too long'),
StringRules.alphanumeric(error: 'Invalid characters'),
]);
emailValidator = Validate.all<String, String>([
StringRules.required(error: 'Email required'),
StringRules.email(error: 'Invalid email'),
]);
passwordValidator = Validate.allCollect<String, String>([
StringRules.required(error: 'Password required'),
StringRules.minLength(8, error: 'Too short'),
StringRules.hasUppercase(error: 'Need uppercase'),
StringRules.hasDigit(error: 'Need digit'),
]);
});
group('username', () {
test('accepts valid usernames', () {
testValidator(
usernameValidator,
validCases: ['john', 'john123', 'JohnDoe', 'user_name'],
invalidCases: ['jo', 'this_username_is_way_too_long', 'with spaces'],
);
});
test('rejects empty username', () {
expect(usernameValidator.validate(''), hasError('Username required'));
});
test('rejects short username', () {
expect(usernameValidator.validate('ab'), hasError('Too short'));
});
});
group('email', () {
test('accepts valid emails', () {
testValidator(
emailValidator,
validCases: [
'test@example.com',
'user.name@domain.org',
'user+tag@company.co.uk',
],
);
});
test('rejects invalid emails', () {
testValidator(
emailValidator,
invalidCases: [
'',
'not-an-email',
'@no-user.com',
'no-domain@',
],
);
});
});
group('password', () {
test('collects all errors', () {
final result = passwordValidator.validate('a');
expect(result, hasErrorCount(3));
expect(result, hasErrorWhere<String>((e) => e.contains('short')));
expect(result, hasErrorWhere<String>((e) => e.contains('uppercase')));
expect(result, hasErrorWhere<String>((e) => e.contains('digit')));
});
test('accepts strong passwords', () {
expect(passwordValidator.validate('StrongP@ss1'), isValid());
});
});
group('fuzz testing', () {
test('handles random input gracefully', () {
final generator = StringGenerator(42);
for (var i = 0; i < 100; i++) {
final random = generator.generate(50);
expect(() => usernameValidator.validate(random), returnsNormally);
expect(() => emailValidator.validate(random), returnsNormally);
expect(() => passwordValidator.validate(random), returnsNormally);
}
});
});
});
}
API Reference #
Matchers #
| Matcher | Description |
|---|---|
isValid() |
Matches valid results |
isInvalid() |
Matches invalid results |
hasError(error) |
Matches result with specific error |
hasErrorWhere<E>(predicate) |
Matches result where error matches predicate |
hasErrorCount(count) |
Matches result with exact error count |
hasValidValue(value) |
Matches valid result with specific value |
Mock Validators #
| Class | Description |
|---|---|
MockValidator<T, E> |
Configure valid/invalid responses |
RecordingValidator<T, E> |
Track all validation calls |
AlwaysValidValidator<T, E> |
Always returns Valid |
AlwaysInvalidValidator<T, E> |
Always returns Invalid |
Helpers #
| Function | Description |
|---|---|
testValidator(...) |
Run validator against multiple cases |
StringGenerator |
Generate random strings |
License #
MIT License - see LICENSE file for details.