mockito 5.0.7 copy "mockito: ^5.0.7" to clipboard
mockito: ^5.0.7 copied to clipboard

outdated

A mock framework inspired by Mockito.

example/example.dart

// ignore_for_file: sdk_version_async_exported_from_core
// ignore_for_file: unawaited_futures
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

// Real class
class Cat {
  String? sound() => 'Meow';
  bool? eatFood(String? food, {bool? hungry}) => true;
  Future<void> chew() async => print('Chewing...');
  int? walk(List<String>? places) => 7;
  void sleep() {}
  void hunt(String? place, String? prey) {}
  int lives = 9;
}

// Mock class
class MockCat extends Mock implements Cat {}

// Fake class
class FakeCat extends Fake implements Cat {
  @override
  bool? eatFood(String? food, {bool? hungry}) {
    print('Fake eat $food');
    return true;
  }
}

void main() {
  late Cat cat;

  setUp(() {
    // Create mock object.
    cat = MockCat();
  });

  test("Let's verify some behaviour!", () {
    // Interact with the mock object.
    cat.sound();

    // Verify the interaction.
    verify(cat.sound());
  });

  test('How about some stubbing?', () {
    // Unstubbed methods return null.
    expect(cat.sound(), null);

    // Stub a method before interacting with it.
    when(cat.sound()).thenReturn('Purr');
    expect(cat.sound(), 'Purr');

    // You can call it again.
    expect(cat.sound(), 'Purr');

    // Let's change the stub.
    when(cat.sound()).thenReturn('Meow');
    expect(cat.sound(), 'Meow');

    // You can stub getters.
    when(cat.lives).thenReturn(9);
    expect(cat.lives, 9);

    // You can stub a method to throw.
    when(cat.lives).thenThrow(RangeError('Boo'));
    expect(() => cat.lives, throwsRangeError);

    // We can calculate a response at call time.
    var responses = ['Purr', 'Meow'];
    when(cat.sound()).thenAnswer((_) => responses.removeAt(0));
    expect(cat.sound(), 'Purr');
    expect(cat.sound(), 'Meow');
  });

  test('Argument matchers', () {
    // You can use plain arguments themselves
    when(cat.eatFood('fish')).thenReturn(true);

    // ... including collections
    when(cat.walk(['roof', 'tree'])).thenReturn(2);

    // ... or matchers
    when(cat.eatFood(argThat(startsWith('dry')))).thenReturn(false);

    // ... or mix aguments with matchers
    when(cat.eatFood(argThat(startsWith('dry')), hungry: true))
        .thenReturn(true);
    expect(cat.eatFood('fish'), isTrue);
    expect(cat.walk(['roof', 'tree']), equals(2));
    expect(cat.eatFood('dry food'), isFalse);
    expect(cat.eatFood('dry food', hungry: true), isTrue);

    // You can also verify using an argument matcher.
    verify(cat.eatFood('fish'));
    verify(cat.walk(['roof', 'tree']));
    verify(cat.eatFood(argThat(contains('food'))));

    // You can verify setters.
    cat.lives = 9;
    verify(cat.lives = 9);

    cat.hunt('backyard', null);
    verify(cat.hunt('backyard', null)); // OK: no arg matchers.

    cat.hunt('backyard', null);
    verify(cat.hunt(argThat(contains('yard')),
        argThat(isNull))); // OK: null is wrapped in an arg matcher.
  });

  test('Named arguments', () {
    // GOOD: argument matchers include their names.
    when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(true);
    when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry')))
        .thenReturn(false);
    when(cat.eatFood(any, hungry: captureAnyNamed('hungry'))).thenReturn(false);
    when(cat.eatFood(any, hungry: captureThat(isNotNull, named: 'hungry')))
        .thenReturn(true);
  });

  test('Verifying exact number of invocations / at least x / never', () {
    cat.sound();
    cat.sound();
    // Exact number of invocations
    verify(cat.sound()).called(2);

    cat.sound();
    cat.sound();
    cat.sound();
    // Or using matcher
    verify(cat.sound()).called(greaterThan(1));

    // Or never called
    verifyNever(cat.eatFood(any));
  });

  test('Verification in order', () {
    cat.eatFood('Milk');
    cat.sound();
    cat.eatFood('Fish');
    verifyInOrder([cat.eatFood('Milk'), cat.sound(), cat.eatFood('Fish')]);
  });

  test('Making sure interaction(s) never happened on mock', () {
    verifyZeroInteractions(cat);
  });

  test('Finding redundant invocations', () {
    cat.sound();
    verify(cat.sound());
    verifyNoMoreInteractions(cat);
  });

  test('Capturing arguments for further assertions', () {
    // Simple capture:
    cat.eatFood('Fish');
    expect(verify(cat.eatFood(captureAny)).captured.single, 'Fish');

    // Capture multiple calls:
    cat.eatFood('Milk');
    cat.eatFood('Fish');
    expect(verify(cat.eatFood(captureAny)).captured, ['Milk', 'Fish']);

    // Conditional capture:
    cat.eatFood('Milk');
    cat.eatFood('Fish');
    expect(
        verify(cat.eatFood(captureThat(startsWith('F')))).captured, ['Fish']);
  });

  test('Waiting for an interaction', () async {
    Future<void> chewHelper(Cat cat) {
      return cat.chew();
    }

    // Waiting for a call.
    chewHelper(cat);
    await untilCalled(cat.chew()); // This completes when cat.chew() is called.

    // Waiting for a call that has already happened.
    cat.eatFood('Fish');
    await untilCalled(cat.eatFood(any)); // This completes immediately.
  });

  test('Fake class', () {
    // Create a new fake Cat at runtime.
    var cat = FakeCat();

    cat.eatFood('Milk'); // Prints 'Fake eat Milk'.
    expect(() => cat.sleep(), throwsUnimplementedError);
  });
}