Viewing version 1.0.5. Open the latest package page.

mocktail

1.0.5

A Dart mock library which simplifies mocking with null safety support and no manual mocks or code generation.

Download archive

Readme

🍹 mocktail

Pub mocktail coverage License: MIT


Mock library for Dart inspired by mockito.

Mocktail focuses on providing a familiar, simple API for creating mocks in Dart (with null-safety) without the need for manual mocks or code generation.

Creating a Mock

import 'package:mocktail/mocktail.dart';

// A Real Cat class
class Cat {
  String sound() => 'meow!';
  bool likes(String food, {bool isHungry = false}) => false;
  final int lives = 9;
}

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

void main() {
  // Create a Mock Cat instance
  final cat = MockCat();
}

Stub and Verify Behavior

The MockCat instance can then be used to stub and verify calls.

// Stub the `sound` method.
when(() => cat.sound()).thenReturn('meow');

// Verify no interactions have occurred.
verifyNever(() => cat.sound());

// Interact with the mock cat instance.
cat.sound();

// Verify the interaction occurred.
verify(() => cat.sound()).called(1);

// When mocktail verifies an invocation, it is then excluded from further verifications.
verifyNever(() => cat.sound());

// Interact with the mock instance again.
cat.sound();

// The verification count is 1 since there was only 1 invocation since the last verification.
verify(() => cat.sound()).called(1);

Additional Usage

// Stub a method before interacting with the mock.
when(() => cat.sound()).thenReturn('purrr!');
expect(cat.sound(), 'purrr!');

// You can interact with the mock multiple times.
expect(cat.sound(), 'purrr!');

// You can change the stub.
when(() => cat.sound()).thenReturn('meow!');
expect(cat.sound(), 'meow');

// You can stub getters.
when(() => cat.lives).thenReturn(10);
expect(cat.lives, 10);

// You can stub a method for specific arguments.
when(() => cat.likes('fish', isHungry: false)).thenReturn(true);
expect(cat.likes('fish', isHungry: false), isTrue);

// You can verify the interaction for specific arguments.
verify(() => cat.likes('fish', isHungry: false)).called(1);

// Or alternatively use any(that: ...) to use a matcher.
verify(() => cat.likes(any(that: isA<String>().having((food) => food, 'name', 'fish')))).called(1);

// You can stub a method using argument matcher: `any`.
// When stubbing a positional argument, use `any()`.
// When stubbing a named argument, use `any(named: '<argName>`)`.
// A custom matcher can be provided using `any(that: customMatcher)`.
when(() => cat.likes(any(), isHungry: any(named: 'isHungry', that: isFalse)).thenReturn(true);
expect(cat.likes('fish', isHungry: false), isTrue);

// You can stub a method to throw.
when(() => cat.sound()).thenThrow(Exception('oops'));
expect(() => cat.sound(), throwsA(isA<Exception>()));

// You can calculate stubs dynamically.
final sounds = ['purrr', 'meow'];
when(() => cat.sound()).thenAnswer((_) => sounds.removeAt(0));
expect(cat.sound(), 'purrr');
expect(cat.sound(), 'meow');

// You can capture any argument.
when(() => cat.likes('fish')).thenReturn(true);
expect(cat.likes('fish'), isTrue);
final captured = verify(() => cat.likes(captureAny())).captured;
expect(captured.last, equals(['fish']));

// You can capture a specific argument based on a matcher.
when(() => cat.likes(any())).thenReturn(true);
expect(cat.likes('fish'), isTrue);
expect(cat.likes('dog food'), isTrue);
final captured = verify(() => cat.likes(captureAny(that: startsWith('d')))).captured;
expect(captured.last, equals(['dog food']));

Resetting Mocks

reset(cat); // Reset stubs and interactions

How it works

Mocktail uses closures to handle catching TypeError instances which would otherwise propagate and cause test failures when stubbing/verifying non-nullable return types. Check out #24 for more information.

In order to support argument matchers such as any and captureAny mocktail has to register default fallback values to return when the argument matchers are used. Out of the box, it automatically handles all primitive types, however, when using argument matchers in place of custom types developers must use registerFallbackValue to provide a default return value. It is only required to call registerFallbackValue once per type so it is recommended to place all registerFallbackValue calls within setUpAll.

class Food {...}

class Cat {
  bool likes(Food food) {...}
}

...

class MockCat extends Mock implements Cat {}

class FakeFood extends Fake implements Food {}

void main() {
  setUpAll(() {
    registerFallbackValue(FakeFood());
  });

  test('...', () {
    final cat = MockCat();
    when(() => cat.likes(any()).thenReturn(true);
    ...
  });
}

FAQs

Why am I getting an invalid_implementation_override error when trying to Fake certain classes like ThemeData and ColorScheme?

Relevant Issue

This is likely due to differences in the function signature of toString for the class and can be resolved using a mixin as demonstrated below:

mixin DiagnosticableToStringMixin on Object {
  @override
  String toString({DiagnosticLevel minLevel = DiagnosticLevel.info}) {
    return super.toString();
  }
}

class FakeThemeData extends Fake
  with DiagnosticableToStringMixin
  implements ThemeData {}

Why can't I stub/verify extension methods properly?

Relevant Issue

Extension methods cannot be stubbed/verified as they are treated like static methods. This means that calls go directly to the extension method without caring about the instance. As a result, stubs and verify calls to extensions always result in an invocation of the real extension method.

Instead of stubbing/verifying extension methods directly, prefer to stub/verify public members on the instance with which the extension methods interact.

Why am I seeing error: type 'Null' is not a subtype of type 'Future'?

Relevant Issue

By default when a class extends Mock any unstubbed methods return null.

For example, take the following class:

class Person {
  Future<void> sleep() {
    await Future<void>.delayed(Duration(hours: 8));
  }
}

We can create a MockPerson like:

class MockPerson extends Mock implements Person {}

If we have code that invokes sleep on MockPerson we will get a TypeError:

type 'Null' is not a subtype of type 'Future<void>'

This is because we did not stub sleep so when sleep is called on an instance of MockPerson, mocktail returns null which is not compatible with Future<void>.

To address this, we must explicitly stub sleep like:

final person = MockPerson();
when(() => person.sleep()).thenAnswer((_) async {});

Why is my method throwing a TypeError when stubbing using any()?

Relevant Issue

By default when a class extends Mock any unstubbed methods return null. When stubbing using any() the type must be inferable. However, when a method has a generic type argument it might not be able to infer the type and as a result, the generic would fallback to dynamic causing the method to act as if it was unstubbed.

For example, take the following class and its method:

class Cache {
  bool set<T>(String key, T value) {
    return true;
  }
}

The following stub will be equivalent to calling set<dynamic>(...):

// The type `T` of `any<T>()` is inferred to be `dynamic`.
when(() => cache.set(any(), any())).thenReturn((_) => true);

To address this, we must explicitly stub set with a type:

final cache = MockCache();
when(() => cache.set<int>(any(), any())).thenReturn((_) => true);
cache.set<int>('key', 1);
verify(() => cache.set<int>(any(), any())).called(1);

The type doesn't need to be applied to set<T>(), any explicit type that allows any<T>() infer its type will allow the method to be stubbed for that type:

when(() => cache.set(any(), any<int>())).thenReturn((_) => true);

Migrating from Mockito

Mocktail's API is very similar to Mockito making the migration as smooth as possible.

The key differences are:

  1. No code generation — remove @GenerateMocks, build_runner, and generated .mocks.dart files.
  2. Wrap calls in closures — stub and verify with () => mock.method() instead of mock.method()
  3. Unified matchers — any(), any(named:), any(that:) replace anyString, anyInt, argThat, etc.

Quick Reference

mockito mocktail
@GenerateMocks([Cat]) + build_runner class MockCat extends Mock implements Cat {}
MockitoAnnotations.initMocks(this) (not needed)
when(mock.sound()).thenReturn('meow') when(() => mock.sound()).thenReturn('meow')
when(mock.sound()).thenAnswer((_) => 'purr') when(() => mock.sound()).thenAnswer((_) => 'purr')
when(mock.sound()).thenThrow(Exception()) when(() => mock.sound()).thenThrow(Exception())
when(mock.fetch(any())) when(() => mock.fetch(any()))
when(mock.fetch(anyNamed('url'))) when(() => mock.fetch(any(named: 'url')))
when(mock.fetch(argThat(startsWith('http')))) when(() => mock.fetch(any(that: startsWith('http'))))
verify(mock.sound()).called(1) verify(() => mock.sound()).called(1)
verifyNever(mock.sound()) verifyNever(() => mock.sound())
verifyInOrder([mock.a(), mock.b()]) verifyInOrder([() => mock.a(), () => mock.b()])
verify(mock.fetch(captureAny)).captured verify(() => mock.fetch(captureAny())).captured
verify(mock.fetch(captureThat(isNotNull))) verify(() => mock.fetch(captureAny(that: isNotNull)))
reset(mock) reset(mock)
resetMockitoState() resetMocktailState()

Sample Migration

Before (mockito)

import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'cat_test.mocks.dart';

@GenerateMocks([Cat])
void main() {
  test('sounds', () {
    final Cat cat = MockCat();
    when(cat.sound()).thenReturn('meow');
    expect(cat.sound(), 'meow');
    verify(cat.sound()).called(1);
  });

  test('eat with matcher', () {
    final Cat cat = MockCat();
    when(cat.eat(argThat(startsWith('fish')))).thenAnswer((_) async => 'yum');
    expect(cat.eat('fishy'), completion(equals('yum')));
    verify(cat.eat('fishy')).called(1);
  });
}

After (mocktail)

import 'package:mocktail/mocktail.dart';

class MockCat extends Mock implements Cat {}

void main() {
  test('sounds', () {
    final Cat cat = MockCat();
    when(() => cat.sound()).thenReturn('meow');
    expect(cat.sound(), 'meow');
    verify(() => cat.sound()).called(1);
  });

  test('eat with matcher', () {
    final Cat cat = MockCat();
    when(() => cat.eat(any(that: startsWith('fish')))).thenAnswer((_) async => 'yum');
    expect(cat.eat('fishy'), completion(equals('yum')));
    verify(() => cat.eat('fishy')).called(1);
  });
}
Changelog

1.0.5

  • chore: minor improvements to example (#266)
  • docs: add mockito migration guide (#265)
  • chore: remove a stray override annotation (#261)
  • chore: add funding to pubspec.yaml (#246)

1.0.4

  • refactor: upgrade analysis_options.yaml (#242)
  • chore: fix minor typo in CHANGELOG.md

1.0.3

  • docs: update README.md to include any(that: ...) (#226)
  • chore: update LICENSE year
  • chore: remove deprecated lint rules

1.0.2

  • chore(deps): allow pkg:test_api >=0.7.0 (#220)
  • docs: update verify example in README.md (#215)

1.0.1

  • chore(deps): replace dependency on package:test with package:test_api to resolve version resolution issues with pkg:analyzer, pkg:test, and flutter_test (#209)
  • chore(deps): upgrade to package:matcher ^0.12.15

1.0.0

  • refactor: use more strict analysis options (#203)
  • docs: adjust LICENSE year
  • docs: add topics to pubspec.yaml

0.3.0

  • BREAKING feat: add support for type argument matching (#66)
  • feat: improve verifyNoMoreInteractions failure message (#118)
  • docs: improve argument matcher documentation in README (#102)
  • docs: fix typo in _registerMatcher inline docs (#101)
  • docs: minor snippet fixes in README (#94)
  • docs: enhance example to illustrate more use cases

0.3.0-dev.1

  • BREAKING feat: add support for type argument matching (#66)
  • docs: minor snippet fixes in README (#94)
  • docs: enhance example to illustrate more use cases

0.2.0

  • BREAKING refactor: remove generic from registerFallbackValue
  • docs: add FAQs to README

0.1.4

  • fix: add built-in fallback for DateTime

0.1.3

  • docs: improve documentation regarding registerFallbackValue and how the library works
  • tests: add additional tests for asyncVoid stubbing and verification

0.1.2

  • feat: improve generic fallback support
  • feat: improve error message for missing registerFallbackValue

0.1.1

  • fix: add missing fallback value (#34)
  • docs: fix broken link in API documentation (#33)

0.1.0

  • feat: add Mock.throwOnMissingStub()
  • fix: make Mock instances immutable
  • chore: convert to test: ^1.16.0

0.0.2-dev.6

  • fix: typo in matcher registration (setUpAll)
  • chore: upgrade to test_api: ^0.3.0

0.0.2-dev.5

  • fix: verify should use arg matcher from invocation if a matcher wasn't provided

0.0.2-dev.4

  • BREAKING fix: verifyInOrder handles multiple method invocation verifications
  • test: 100% coverage
  • docs: additional inline API documentation updates

0.0.2-dev.3

  • BREAKING fix: verifyInOrder function signature
  • docs: update inline API documentation
  • test: add nnbd compat tests

0.0.2-dev.2

  • chore: documentation updates

0.0.2-dev.1

  • BREAKING refactor: removed the of parameter of matchers in exchange for a registerFallbackValue

0.0.2-dev.0

  • BREAKING refactor: revamp mocktail API to be typesafe instead of relying on Symbol

0.0.1-dev.12

  • fix: any matcher with no value

0.0.1-dev.11

  • chore: bump dependencies
    • matcher: ^0.12.10
    • test: ^1.16.0

0.0.1-dev.10

  • feat: thenReturn argument is optional for VoidCallback returns
    • when(cat).calls(#makeSound).thenReturn()
  • feat: add support for captured with captureAny and captureAnyThat matchers
  • fix: verify fixes for default arguments (named and positional)

0.0.1-dev.9

  • fix: call verification count per specific real invocation

0.0.1-dev.8

  • fix: throw NoSuchMethodError when arguments match but memberName does not

0.0.1-dev.7

  • BREAKING refactor: rename verify(mock).calls(#member) to verify(mock).called(#member)
  • feat: add any argument matcher support to verify and calls
  • feat: add anyThat argument matcher support to verify and calls
  • feat: support verify(mock).called(#member).once()
  • feat: support verify(mock).called(#member).never()
  • feat: improve error messages to include invocation arguments

0.0.1-dev.6

  • BREAKING refactor: use Symbol rather than String
  • fix: strict argument matching on verification when arguments are provided

0.0.1-dev.5

  • fix: override toString on MocktailFailure to improve visibility of failure messages

0.0.1-dev.4

  • feat: add Matcher support to verify times API
  • docs: minor usage documentation updates in README

0.0.1-dev.3

  • BREAKING refactor: rename positionalArgs to positionalArguments
  • BREAKING refactor: rename namedArgs to namedArguments
  • fix: thenAnswer callback contains correct Invocation
  • docs: minor documentation updates

0.0.1-dev.2

  • docs: minor updates to README
  • docs: minor updates to example

0.0.1-dev.1

  • feat: initial release 🎉

Installing

export PUB_HOSTED_URL=https://pub.mirror.p-hatan.ir
flutter pub add 'mocktail:1.0.5'
pubspec.yaml
name: mocktail
description: A Dart mock library which simplifies mocking with null safety support and no manual mocks or code generation.
version: 1.0.5
repository: https://github.com/felangel/mocktail
homepage: https://github.com/felangel/mocktail/tree/main/packages/mocktail
topics: [mock, test]
funding: [https://github.com/sponsors/felangel]

environment:
  sdk: ">=2.12.0 <4.0.0"

dependencies:
  collection: ^1.15.0
  matcher: ^0.12.15
  test_api: '>=0.2.1 <0.8.0'

dev_dependencies:
  meta: ^1.10.0
  test: ^1.24.6

Version history

VersionPublishedArchiveSHA-256
0.0.1-dev.1On demandDownload5b64faab…64ab44
0.0.1-dev.2On demandDownload6e3f7012…c5dca3
0.0.1-dev.3On demandDownload98a4584e…76e545
0.0.1-dev.4On demandDownload69dc31a3…f09e52
0.0.1-dev.5On demandDownload22d467c5…a22d8b
0.0.1-dev.6On demandDownload0238e210…ecb3fa
0.0.1-dev.7On demandDownloade0bff4eb…b30a5c
0.0.1-dev.8On demandDownload1a5112cc…9b4803
0.0.1-dev.9On demandDownload729227f1…cee519
0.0.1-dev.10On demandDownloadf09812f3…95f6a3
0.0.1-dev.11On demandDownloadcdb4128d…e8e370
0.0.1-dev.12On demandDownloade613170b…33b8bc
0.0.2-dev.0On demandDownloadda54e59a…1962b9
0.0.2-dev.1On demandDownload782b9e38…86d236
0.0.2-dev.2On demandDownload51ea1e51…923813
0.0.2-dev.3On demandDownloadf5d75b09…52ca9a
0.0.2-dev.4On demandDownload4d96765c…9244e8
0.0.2-dev.5On demandDownloade1e2c368…9f0d0a
0.0.2-dev.6On demandDownload3641573a…24ad44
0.1.0On demandDownload803b72ef…622bb9
0.1.1On demandDownload84982525…0c531d
0.1.2On demandDownload77b290ea…21e7c5
0.1.3On demandDownloadb7774b14…a6502c
0.1.4On demandDownloadac49ddf0…6c9809
0.2.0On demandDownloaddd85ca52…9e4dc2
0.3.0-dev.1On demandDownload328fbe54…79ff23
0.3.0On demandDownload80a996cd…0cef53
1.0.0-dev.0On demandDownload3b5b76b6…7d5807
1.0.0On demandDownload9503969a…9a5a58
1.0.1On demandDownloadbac151b3…f596c1
1.0.2On demandDownloadf603ebd8…bb365e
1.0.3On demandDownloadc4b5007d…77dce6
1.0.4On demandDownload890df3f9…f172d8
1.0.5Cached · Upstream SHA-256 matchedDownload5e1bf53c…5914aa

Dependencies

collection "^1.15.0"

matcher "^0.12.15"

test_api "\u003e=0.2.1 \u003c0.8.0"