formix_test 0.1.0 copy "formix_test: ^0.1.0" to clipboard
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.

0
likes
150
points
113
downloads

Publisher

unverified uploader

Weekly Downloads

Testing utilities for the Formix ecosystem. Includes custom matchers, mock validators, and test helpers for validation testing.

Homepage
Repository (GitHub)
View/report issues

Topics

#validation #testing #matchers #test-utilities #form-validation

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

formix_core, matcher, test

More

Packages that depend on formix_test