Mockito for Dart

repository·master·Indexed 20 days ago

https://github.com/dart-archive/mockito

A mock library for Dart providing tools for stubbing method behavior and verifying interactions. It supports Dart null safety via code generation using @GenerateNiceMocks and @GenerateMocks. The library includes utilities for stubbing asynchronous methods with thenAnswer, verifying call sequences with verifyInOrder, and creating simplified fake classes using Fake.

Tokens
7.8K
Snippets
25
Records
33
Agent score
71%

What's inside mockito

  1. Mocking static methods, constructors, or top-level functions

    master

    Mockito cannot mock static methods, constructors, or top-level functions because it relies on overriding class instance methods via noSuchMethod.

    To make your code testable, consider these refactoring patterns:

    1. Dependency Injection: Instead of constructing an object inside a function, pass the object as an argument.
    // BEFORE: Un-mockable constructor call
    void f() {
      var foo = Foo();
      // ...
    }
    
    // AFTER: Inject the dependency
    void f(Foo foo) {
      // ...
    }

    In your test, you can then pass a MockFoo instance to f.

    1. Wrapper Systems: Use a wrapper or a service to abstract away static/global calls. For example, instead of using Directory.current or File(), use the file package's FileSystem abstraction, which allows you to swap a LocalFileSystem for a MemoryFileSystem during tests.
    // BEFORE:
    void f() {
      var foo = Foo();
      // ...
    }
    
    // AFTER
    void f(Foo foo) {
      // ...
    }
  2. Use argument matchers for flexible verification

    master

    Mockito uses ArgMatcher to allow flexible matching of arguments in when() and verify().

    Important Constraints:

    • You cannot use null as an argument adjacent to an ArgMatcher (e.g., verify(cat.hunt(argThat(contains('yard')), null)) is invalid).
    • To match null, you must wrap it in a matcher like argThat(isNull).
    • For named arguments, you must explicitly provide the name of the argument to the matcher.
    // Using matchers
    when(cat.eatFood(any)).thenReturn(false);
    when(cat.eatFood(argThat(startsWith("dry")))).thenReturn(false);
    
    // Named arguments REQUIRE the name
    when(cat.eatFood(any, hungry: anyNamed('hungry'))).thenReturn(true);
    when(cat.eatFood(any, hungry: argThat(isNotNull, named: 'hungry'))).thenReturn(false);
    
    // Handling nulls with matchers
    verify(cat.hunt(argThat(contains("yard")), argThat(isNull))); // OK
  3. Understand limitations of argument matchers in Null Safety

    master
    In Dart with Null Safety, traditional Mockito argument matchers like any, argThat, and captureAny return null. This causes runtime errors when these matchers are used in place of non-nullable parameters (e.g., an int or a non-nullable class), because null is no longer a valid value for those types. To resolve this, you must use Solution 1: code generation (using build_runner) to create mocks that are compatible with null safety.
  4. How Mockito works internally

    master

    Mockito uses the noSuchMethod hook to intercept method calls on Mock objects.

    The when() mechanism

    when() is a top-level getter that returns a function. When when() is invoked, it sets an internal flag (_whenInProgress). While this flag is true, all Mock objects return a special matcher (_WhenCall) instead of their configured return value. Once the when() call completes, the flag is reset.

    Warning: Never write when; as a standalone statement. This will leave _whenInProgress set to true, causing subsequent mock calls to return unexpected values.

    Argument Matchers

    Argument matchers store wrapped arguments sequentially. For positional arguments, the order is preserved. For named arguments, Mockito requires the matcher to repeat the argument name (e.g., foo: anyNamed('foo')) to ensure correct mapping, as the evaluation order of named arguments in Dart is not guaranteed.

  5. How Nice Mocks vs Classic Mocks differ

    master

    Mockito provides two ways to generate mocks, which differ in their "missing stub" behavior (what happens when a method is called without a corresponding when(...) declaration):

    • @GenerateNiceMocks (Recommended): Returns a "simple" legal value (e.g., a non-null value for a non-nullable return type) to avoid runtime type exceptions. These values should not be relied upon for logic.
    • @GenerateMocks: Throws an exception if a method is called without a stub.
  6. Why method calls cannot be verified multiple times

    master

    When you call verify or verifyInOrder, Mockito marks that specific invocation as "verified", which excludes it from subsequent verification attempts.

    If you attempt to verify the same call with different matchers, the second call will fail because the first call has already been consumed by the first verify statement.

    To perform multiple assertions on the same call (e.g., checking arguments and then checking the number of calls), you should save the verification object or use the .captured property.

    cat.eatFood("fish");
    verify(cat.eatFood("fish"));  // This call succeeds.
    verify(cat.eatFood(any));  // This call fails.
    
    // Correct way to perform multiple assertions:
    cat.hunt("home", "birds");
    var captured = verify(cat.hunt(captureAny, captureAny)).captured.single;
    expect(captured[0], equals("home"));
    expect(captured[1], equals("birds"));
    
    // Correct way to verify call count AND capture arguments:
    cat.hunt("home", "birds");
    cat.hunt("home", "lizards");
    var verification = verify(cat.hunt("home", captureAny));
    verification.called(greaterThan(2));
    var firstCall = verification.captured[0];
    var secondCall = verification.captured[1];
    
    expect(firstCall, equals(["birds"]));
    expect(secondCall, equals(["lizards"]));
  7. Best practices for using mocks and fakes

    master

    Follow these guidelines to ensure reliable and maintainable tests:

    • Prefer real objects: If you can construct a real instance, do so. Use mocks only when necessary.
    • Use Fakes over Mocks: A tested implementation of a Fake is often better than a Mock because it behaves more like the real class.
    • Don't stub in constructors: A class extending Mock should never use when inside its own constructor. Define stubs within the test where they are used.
    • Keep Mocks pure: A class extending Mock should not have any implementation (no @override methods or mixins) other than static utilities. Mixing manual overrides with Mockito's stubbing leads to confusion and prevents Mockito from tracking/verifying calls correctly.
    • Data models: Never mock data models; construct them with real or stubbed data instead.
  8. Understand limitations of return types in Null Safety

    master
    Mockito's base Mock class implements methods by overriding noSuchMethod and returning null. Under Null Safety, if a method is defined to return a non-nullable type, the null returned by the noSuchMethod implementation will cause a type error. To use mocks for methods with non-nullable return types, you must use Solution 1: code generation to generate mocks that provide valid non-null return values during when or verify calls.
  9. Migrate from Mockito 2.x to 3.x

    master

    Mockito 3 introduces a type-safe API compatible with Dart 2's type rules. To avoid breaking a large codebase, use an incremental upgrade path via 3.0.0-alpha+4 before moving to the final 3.0.0 release.

    Incremental Upgrade Workflow

    1. Step 1: Intermediate Upgrade Update your pubspec.yaml to use the backward-compatible version:
      mockito: '^3.0.0-alpha+4'
       Commit this change. This version still uses the Mockito 2.x implementation internally, so your existing tests should not break.
    
    2. **Step 2: Refactor API Calls**
       Search your codebase for `when(`, `verify(`, `verifyNever(`, `typed(`, and `named:` to identify old patterns. Replace them using the migration rules (see [Migration Cheatsheet](#migration-cheatsheet)).
    
    3. **Step 3: Final Upgrade**
       Once all tests pass with the refactored code, update `pubspec.yaml` to the stable version:
       ```yaml
    mockito: '^3.0.0'

    Commit the change. In Mockito 3, typed is deprecated and acts as a no-op.

  10. Refactor Mockito named argument matchers

    master

    When upgrading to Mockito 3, any matcher used as a named argument must be updated to include the argument name as a String.

    Rules for named arguments:

    • foo: any $\rightarrow$ foo: anyNamed('foo')
    • foo: argThat(...) $\rightarrow$ foo: argThat(..., named: 'foo')
    • foo: captureAny $\rightarrow$ foo: captureAnyNamed('foo')
    • foo: captureThat(...) $\rightarrow$ foo: captureThat(..., named: 'foo')

    Rules for removing typed wrappers:

    • typed(any) $\rightarrow$ any
    • foo: typed(any, named: 'foo') $\rightarrow$ foo: anyNamed('foo')

    Rule for nulls:

    • Any bare null argument should be rewritten as argThat(isNull).
  11. Create mocks using code generation

    master

    Mockito 5.0.0+ uses code generation to support Dart null safety. To generate mock classes, use the @GenerateNiceMocks annotation (recommended) or @GenerateMocks on an import statement.

    1. Add build_runner to your dev_dependencies in pubspec.yaml.
    2. Annotate the import of your generated .mocks.dart file.
    3. Run the build command to generate the mock classes.

    Note: By default, Mockito only processes annotations in files under test/. To use them elsewhere, you must configure a build.yaml file.

    import 'package:mockito/annotations.dart';
    import 'package:mockito/mockito.dart';
    
    // Annotation which generates the cat.mocks.dart library and the MockCat class.
    @GenerateNiceMocks([MockSpec<Cat>()])
    import 'cat.mocks.dart';
    
    class Cat {
      String sound() => "Meow";
      // ...
    }
    
    void main() {
      var cat = MockCat();
    }
    dart run build_runner build
  12. Manually implement mocks for null safety

    master

    While code generation is recommended, you can manually implement a mock class if you only need to mock a single class or cannot use build_runner.

    To support null safety, you must override every public method that has a non-nullable parameter or a non-nullable return type.

    Overriding non-nullable parameters

    Expand the parameter type to be nullable (add ?) and call super.noSuchMethod with an Invocation.method object.

    Overriding non-nullable return types

    Override the getter/method and call super.noSuchMethod with an Invocation.getter (or .method) and provide a returnValue that satisfies the non-nullable type contract. This return value is used only to satisfy the type system; Mockito will use your when(...).thenReturn(...) values during actual test execution.

    class MockHttpServer extends Mock implements HttpServer {
      // Overriding a method with a non-nullable parameter
      @override
      void start(int? port) =>
          super.noSuchMethod(Invocation.method(#start, [port]));
    
      // Overriding a getter with a non-nullable return type
      @override
      Uri get uri =>
          super.noSuchMethod(
              Invocation.getter(#uri), 
              returnValue: Uri.http('example.org', '/')
          );
    }