Coder Social home page Coder Social logo

mockito's Introduction

Dart CI Pub package publisher

Mock library for Dart inspired by Mockito.

Let's create mocks

Mockito 5.0.0 supports Dart's new null safety language feature in Dart 2.12, primarily with code generation.

To use Mockito's generated mock classes, add a build_runner dependency in your package's pubspec.yaml file, under dev_dependencies; something like build_runner: ^1.11.0.

For alternatives to the code generation API, see the NULL_SAFETY_README.

Let's start with a Dart library, cat.dart:

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';

// 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;
}

void main() {
  // Create mock object.
  var cat = MockCat();
}

By annotating the import of a .mocks.dart library with @GenerateNiceMocks, you are directing Mockito's code generation to write a mock class for each "real" class listed, in a new library.

The next step is to run build_runner in order to generate this new library:

flutter pub run build_runner build
# OR
dart run build_runner build

build_runner will generate a file with a name based on the file containing the @GenerateNiceMocks annotation. In the above cat.dart example, we import the generated library as cat.mocks.dart.

NOTE: by default only annotations in files under test/ are processed, if you want to add Mockito annotations in other places, you will need to add a build.yaml file to your project, see this SO answer.

The generated mock class, MockCat, extends Mockito's Mock class and implements the Cat class, giving us a class which supports stubbing and verifying.

Let's verify some behavior!

// Interact with the mock object.
cat.sound();
// Verify the interaction.
verify(cat.sound());

Once created, the mock instance will remember all interactions. Then you can selectively verify (or verifyInOrder, or verifyNever) the interactions you are interested in.

How about some stubbing?

// Stub a mock method before interacting.
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");

// We can stub a method with multiple calls that happened in a particular order.
when(cat.sound()).thenReturnInOrder(["Purr", "Meow"]);
expect(cat.sound(), "Purr");
expect(cat.sound(), "Meow");
expect(() => cat.sound(), throwsA(isA<StateError>()));

The when, thenReturn, thenAnswer, and thenThrow APIs provide a stubbing mechanism to override this behavior. Once stubbed, the method will always return stubbed value regardless of how many times it is called. If a method invocation matches multiple stubs, the one which was declared last will be used. It is worth noting that stubbing and verifying only works on methods of a mocked class; in this case, an instance of MockCat must be used, not an instance of Cat.

A quick word on async stubbing

Using thenReturn to return a Future or Stream will throw an ArgumentError. This is because it can lead to unexpected behaviors. For example:

  • If the method is stubbed in a different zone than the zone that consumes the Future, unexpected behavior could occur.
  • If the method is stubbed to return a failed Future or Stream and it doesn't get consumed in the same run loop, it might get consumed by the global exception handler instead of an exception handler the consumer applies.

Instead, use thenAnswer to stub methods that return a Future or Stream.

// BAD
when(mock.methodThatReturnsAFuture())
    .thenReturn(Future.value('Stub'));
when(mock.methodThatReturnsAStream())
    .thenReturn(Stream.fromIterable(['Stub']));

// GOOD
when(mock.methodThatReturnsAFuture())
    .thenAnswer((_) async => 'Stub');
when(mock.methodThatReturnsAStream())
    .thenAnswer((_) => Stream.fromIterable(['Stub']));

If, for some reason, you desire the behavior of thenReturn, you can return a pre-defined instance.

// Use the above method unless you're sure you want to create the Future ahead
// of time.
final future = Future.value('Stub');
when(mock.methodThatReturnsAFuture()).thenAnswer((_) => future);

Argument matchers

Mockito provides the concept of the "argument matcher" (using the class ArgMatcher) to capture arguments and to track how named arguments are passed. In most cases, both plain arguments and argument matchers can be passed into mock methods:

// You can use `any`
when(cat.eatFood(any)).thenReturn(false);

// ... or 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 arguments 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);

If an argument other than an ArgMatcher (like any, anyNamed, argThat, captureThat, etc.) is passed to a mock method, then the equals matcher is used for argument matching. If you need more strict matching, consider using argThat(identical(arg)).

However, note that null cannot be used as an argument adjacent to ArgMatcher arguments, nor as an un-wrapped value passed as a named argument. For example:

verify(cat.hunt("backyard", null)); // OK: no arg matchers.
verify(cat.hunt(argThat(contains("yard")), null)); // BAD: adjacent null.
verify(cat.hunt(argThat(contains("yard")), argThat(isNull))); // OK: wrapped in an arg matcher.
verify(cat.eatFood("Milk", hungry: null)); // BAD: null as a named argument.
verify(cat.eatFood("Milk", hungry: argThat(isNull))); // BAD: null as a named argument.

Named arguments

Mockito currently has an awkward nuisance to its syntax: named arguments and argument matchers require more specification than you might think: you must declare the name of the argument in the argument matcher. This is because we can't rely on the position of a named argument, and the language doesn't provide a mechanism to answer "Is this element being used as a named element?"

// 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);

// BAD: argument matchers do not include their names.
when(cat.eatFood(any, hungry: any)).thenReturn(true);
when(cat.eatFood(any, hungry: argThat(isNotNull))).thenReturn(false);
when(cat.eatFood(any, hungry: captureAny)).thenReturn(false);
when(cat.eatFood(any, hungry: captureThat(isNotNull))).thenReturn(true);

Verifying exact number of invocations / at least x / never

Use verify or verifyNever:

cat.sound();
cat.sound();

// Exact number of invocations
verify(cat.sound()).called(2);

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

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

Verification in order

Use verifyInOrder:

cat.eatFood("Milk");
cat.sound();
cat.eatFood("Fish");
verifyInOrder([
  cat.eatFood("Milk"),
  cat.sound(),
  cat.eatFood("Fish")
]);

Verification in order is flexible - you don't have to verify all interactions one-by-one but only those that you are interested in testing in order.

Making sure interaction(s) never happened on mock

Use verifyZeroInteractions:

verifyZeroInteractions(cat);

Finding redundant invocations

Use verifyNoMoreInteractions:

cat.sound();
verify(cat.sound());
verifyNoMoreInteractions(cat);

Capturing arguments for further assertions

Use the captureAny, captureThat, and captureAnyNamed argument matchers:

// 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"]);

Waiting for an interaction

Use untilCalled:

// Waiting for a call.
cat.eatFood("Fish");
await untilCalled(cat.chew()); // Completes when cat.chew() is called.

// Waiting for a call that has already happened.
cat.eatFood("Fish");
await untilCalled(cat.eatFood(any)); // Completes immediately.

Nice mocks vs classic mocks

Mockito provides two APIs for generating mocks, the @GenerateNiceMocks annotation and the @GenerateMocks annotation. The recommended API is @GenerateNiceMocks. The difference between these two APIs is in the behavior of a generated mock class when a method is called and no stub could be found. For example:

void main() {
  var cat = MockCat();
  cat.sound();
}

The Cat.sound method returns a non-nullable String, but no stub has been made with when(cat.sound()), so what should the code do? What is the "missing stub" behavior?

  • The "missing stub" behavior of a mock class generated with @GenerateMocks is to throw an exception.
  • The "missing stub" behavior of a mock class generated with @GenerateNiceMocks is to return a "simple" legal value (for example, a non-null value for a non-nullable return type). The value should not be used in any way; it is returned solely to avoid a runtime type exception.

Mocking a Function type

To create mocks for Function objects, write an abstract class with a method for each function type signature that needs to be mocked. The methods can be torn off and individually stubbed and verified.

@GenerateMocks([Cat, Callbacks])
import 'cat_test.mocks.dart'

abstract class Callbacks {
  Cat findCat(String name);
}

void main() {
  var mockCat = MockCat();
  var findCatCallback = MockCallbacks().findCat;
  when(findCatCallback('Pete')).thenReturn(mockCat);
}

Writing a fake

You can also write a simple fake class that implements a real class, by extending Fake. Fake allows your subclass to satisfy the implementation of your real class, without overriding the methods that aren't used in your test; the Fake class implements the default behavior of throwing UnimplementedError (which you can override in your fake class):

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

void main() {
  // Create a new fake Cat at runtime.
  var cat = FakeCat();

  cat.eatFood("Milk"); // Prints 'Fake eat Milk'.
  cat.sleep(); // Throws.
}

Resetting mocks

Use reset:

// Clearing collected interactions:
cat.eatFood("Fish");
clearInteractions(cat);
cat.eatFood("Fish");
verify(cat.eatFood("Fish")).called(1);

// Resetting stubs and collected interactions:
when(cat.eatFood("Fish")).thenReturn(true);
cat.eatFood("Fish");
reset(cat);
when(cat.eatFood(any)).thenReturn(false);
expect(cat.eatFood("Fish"), false);

Debugging

Use logInvocations and throwOnMissingStub:

// Print all collected invocations of any mock methods of a list of mock objects:
logInvocations([catOne, catTwo]);

// Throw every time that a mock method is called without a stub being matched:
throwOnMissingStub(cat);

Best Practices

Testing with real objects is preferred over testing with mocks - if you can construct a real instance for your tests, you should! If there are no calls to verify in your test, it is a strong signal that you may not need mocks at all, though it's also OK to use a Mock like a stub. Data models never need to be mocked if they can be constructed with stubbed data. When it's not possible to use the real object, a tested implementation of a fake is the next best thing; it's more likely to behave similarly to the real class than responses stubbed out in tests. Finally an object which extends Fake using manually overridden methods is preferred over an object which extends Mock used as either a stub or a mock.

A class which extends Mock should never stub out its own responses with when in its constructor or anywhere else. Stubbed responses should be defined in the tests where they are used. For responses controlled outside of the test use @override methods for either the entire interface, or with extends Fake to skip some parts of the interface.

Similarly, a class which extends Mock should never have any implementation. It should not define any @override methods, and it should not mixin any implementations. Actual member definitions can't be stubbed by tests and can't be tracked and verified by Mockito. A mix of test defined stubbed responses and mock defined overrides will lead to confusion. It is OK to define static utilities on a class which extends Mock if it helps with code structure.

Frequently asked questions

Read more information about this package in the FAQ.

mockito's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

mockito's Issues

Mockito 2.2.0 is stricter in a few, undocumented ways.

In upgrading a large corpus of tests to Mockito 2.2.0, I've found a few undocumented new restrictions. I think it would be good to document them:

  • captureAny args used to be captured when captureAny was used in when, and that functionality has disappeared. It wasn't explicitly supported before, but I'd still like this change documented.

  • matching used to be looser when not using argThat(equals(...)). For example:

    import 'package:mockito/mockito.dart';
    import 'package:test/test.dart';
    
    class MockedClass extends Mock implements RealClass {}
    
    class RealClass {
      String methodWithListArgs(Iterable<int> x) => "Real"
    }
    
    void main() {
      test("should mock method with List args", () {
        when(mock.methodWithListArgs([42])).thenReturn("Ultimate answer");
        var ary = [42].map((e) => e * 2 / 2); // ary is an Iterable that looks like [42].
        expect(mock.methodWithListArgs(ary), equals("Ultimate answer"));
      }
    }

    fails. It did not used to. expect(mock.methodWithListArgs(argThat(equals(ary))), ... works though.

  • stubs that return Future are behaving weird for me... have not pinned down yet.

Push users to .thenAnswer(<Future|Stream>)?

This is what I see a lot:

var mockThing = new MockThing();
when(mock.isDone).thenReturn(new Future.value(true));

// ...
runTest(thing: mockThing);

But it can break in basically un-debuggable ways, especially around zones. Normally a Future or Stream won't be created until an invocation is made, but with the .thenReturn pattern, it has been created in the test setup.

The "correct" behavior here would be to use .thenAnswer:

var mockThing = new MockThing();
when(mock.isDone).thenAnswer((_) => new Future.value(true));

I don't know if this a documentation, API, or just UX issue, or there is even a real issue - maybe mockito alone is mysterious so nobody assumes code to work great 100% of the time. We could make the API more verbose, i.e.:

thenAnswerSync<T>(T value);
thenAnswerAsync<T>(T Function() run);

But I don't know if that would help unless we threw on an invalid type at runtime.

Add capturedAs<T>() helper?

Not high priority, but the unfortunate fact that captured is a List<dynamic> does not play well with the Dart 2 concept of not-using-dynamic-as-bottom. For example:

cat.eatFood("Milk");
cat.eatFood("Fish");
expect(
    verify(cat.eatFood(captureAny)).captured.every((String s) => s.isNotEmpty), isTrue);

This code treats dynamic as bottom, by passing a Function(String) where a Function(dynamic) is expected. In Dart 2 you have cast<T>() and retype<T>() at your fingertips, so you can do:

expect(
    verify(cat.eatFood(captureAny))
        .captured
        .retype<String>()
        .every((String s) => s.isNotEmpty),
    isTrue);

which (again, not high priority) either adds a few characters to your statement, or a new line (or forces line wrapping). We could provide capturedAs<T>():

expect(
    verify(cat.eatFood(captureAny))
        .capturedAs<String>()
        .every((String s) => s.isNotEmpty),
    isTrue);

One problem is how do you treat function invocations with multiple captured arguments...

Flip mockito.dart with mockito_no_mirrors.dart

I think it would be worth it just to have package:mockito/mockito.dart not include mirrors.

The only extra function Mockito provides for mirrors users is spy:

dynamic spy(dynamic mock, dynamic spiedObject) {
  var mirror = reflect(spiedObject);
  setDefaultResponse(
      mock,
      () => new CannedResponse(null,
          (Invocation realInvocation) => mirror.delegate(realInvocation)));
  return mock;
}

I am not even sure there is usage of this and could be convinced to just remove it.

/cc @srawlins @TedSander

Ability to mock classes using a 'mock()' function instead of having to define a Mock class

One of the things that's convenient about the Java Mockito library is that there is no need to explicitly define a Mock class. Currently when writing a Dart unit test the developer has to define these mock classes. This usually happens in the test file itself and sometime even duplicated across test files. This increases the friction when writing unit tests.

It would be convenient to just create a mock instance with something like

Cat mockCat = mock(Cat)

where mock() is a top-level function of mockito. This makes it similar to the Java library.

The implementation would be a Dart source transformer that parses mock(Type) and generates the Mock class and replaces the mock() call with new MockCat() (assuming MockCat is the name of the generated class - might be different in the actual implementation). mock(Type) might itself just be a mockito library function of return type dynamic that just returns null to keep the IDE happy.

Of course there will have to be considerations around warnings/errors to tell the user if they try to pass in anything other than a type that's known at compile time.

Has this been considered before? Please let me know and any comments as I would be interested in implementing this.

.captured sometimes returns strange closures

I have this calls:

VerificationResult result = verify(transaction.queueMutations(inserts: captureThat(isList)));
print(result.callCount);
print(result.captured.length);
result.captured.forEach(print);

and the output is:

6
9
Closure: (Transaction) => dynamic
Closure: (Transaction) => dynamic
Closure: (Transaction) => dynamic
[Transaction.69140b7c-742f-469f-b039-303af8390c67]
[Instance of 'User']
[Transaction.69140b7c-742f-469f-b039-303af8390c67]
[Instance of 'User']
[Transaction.69140b7c-742f-469f-b039-303af8390c67]
[Instance of 'User']

First of all: it seems very strange that captured calls for captureThat(isList) would return closures.
Then it seems strange that the .callCount differs from .captured.length.
And lastly: where do those Closures come from?

I haven't found any better way to debug this, but maybe you could guide me in the right direction to get to the bottom of this.

Thanks

EDIT: For the record: the last 6 entities in the result.captured list are the expected values.

Allow `any` to work with Strong mode

This is a sibling issue to dart-lang/sdk#26036.

The issue is that we cannot use any with Strong mode turned on. For example:

int f(List<int> l) => l[0];

void main() {
  f(any);
}

Yields an analyzer error (dartanalyzer --strong a.dart): "Unsound implicit cast from dynamic to List".

I think we can solve this on the Mockito side, similar to Java Mockito's any().

I have a prototype implementation that uses the Strong mode type parameter comments:

/*=T*/ any/*<T>*/([/*=T*/ e = null]) => null;

The tricky part is that any would now return null, instead of the _ArgMatcher (again similar to Java Mockito's, and we would have to implement something like Java Mockito's reportMatcher(), maybe changing how the whole matching situation works...

What do you think? Any thoughts or other ideas?

Handle method tear-off on mock

It would be great to be able to have

    test("should verify tear-off", () {
      final f = mock.methodWithoutArgs;
      f();
      verify(mock.methodWithoutArgs());
    });

Use-case

I was refactoring some code from

for (var i in list)
  o.m(i);

to

list.foreach(o.m);

This change triggers several test failures:

  NoSuchMethodError: The method 'call' was called on null.
  Receiver: null
  Tried calling: call()

Mockito without mirrors?

Would it be possible to create a version of mockito that doesn't use dart:mirrors? That would enable mockito to be used in environments that disable the mirrors support.

New Dart 2 semantics Mockito API

We need to bump Mockito to be Dart 2-only, so that we can remove the pesky typed wrapper, so that everything is just any, argThat, captureArg, etc.

However, the new Mockito API will kind of have to adhere to the typed shortcoming of having to name named arguments by name, i.e. fn(argThat(equals(3), number: argThat(equals(7), named: 'number')). Boooo. Alas, this is unavoidable until we go to a code-genned Mockito, which is a bigger migration than this, and is not necessary until non-nullable types.

Which leads us to: how do we call any and captureAny, which are getters today, and have such marvelous ergonomics, when used with a named argument? 😦 Getters cannot take arguments.

Option A: We split the methods

Null get any => ...
Null anyNamed(String named) => ...
Null get captureAny => ...
Null captureAnyNamed(String named) => ...

This adds confusion because now named is a positional arg in anyNamed and captureAnyNamed, but a named arg in argThat and captureThat. 😦 (Option A2 can be where anyNamed and captureAnyNamed use a named argument for named.)

Option B: we force any to no longer be a getter, ever 😦 :

Null any({String named}) => ...
Null captureAny({String named}) => ...

This would mean using fn(any(), any(), any()) everywhere. Worse ergonomics, and really confusing and frustrating for people during migration.

Option C: Ugh, we go to codegen now

I don't want to do this. It will be huge migration, make Mockito harder to use where codegenning isn't super easy and streamlined, take longer, ... But it does remove the need to use named everywhere.

Migrate syntax to be 2.0-primary

Today a user can write when(cat.eatFood(any))...;, in Dart 1. This has always been odd because eatFood accepts a String, not an ArgMatcher (the return value of any). But Dart 1 was okie dokie with this, and it allowed for a great mocking library. Dart 2 is not ok with this, hence the Strong Mode compliance API. We should switch mockito to ultimately only support the Dart 2 type system, i.e. make when(cat.eatFood(any))...; look like today's when(cat.eatFood(typed(any)))...;.

This is absolutely a breaking change, and will be the primary feature that bumps us to Mockito 3.0. It also prevents some old mocking behavior:

when(obj.fn(any, null)) will no longer be allowed (null in arg list with any), as mentioned in the strong mode compliance docs. We'll require when(obj.fn(any, argThat(equals(null))). I think we can introduce nullArg or something...

We can also keep typed around as an identity function, to allow users to migrate gracefully (except the case above).

Publish latest to pub ? (to include updated README)

Hi,

Would it be possible to publish the latest version of this to pub.dartlang.org? Specifically, the README displayed on pub.dartlang.org doesn't include the hint to add nSM to your class so you can silence the analyzer warnings.

However, the latest version of the README does have that very useful hint.

Thanks!

Discussion: returns stub instead of null for first class citizen member function

Hi,

In dart, member function can be used as first class citizen to simplify the code. Considering following code

void willCallCallback(void callback()) {
   ... do something
   callback();
}
abstract class HasFoo { void foo(); }
functionNeedToBeTested(HasFoo hasFoo) {
   ... do something
   return () { 
      ... do something
      willCallCallback(hasFoo.foo);
   };
}

I want to test the functionNeedToBeTested, but I can not simply write it as

var mockHasFoo = ...
var block = functionNeedToBeTested(mockHasFoo);
block();
verify(mockHasFoo.foo());

A full example:

typedef void UnaryFunction();

class HasCallback {
  final UnaryFunction _fooCallback;
  final UnaryFunction _barCallback;

  HasCallback(this._fooCallback, this._barCallback);

  void foo() {
    // Do something
    _fooCallback();
  }

  void bar() {
    // Do something
    _barCallback();
  }
}

class FunctionProvider {
  void abc() {
    print('abc is called');
  }
  void xyz() {
    print('xyz is called');
  }
}

HasCallback toBeTested(FunctionProvider funcProvider) {
  // Do something
  return new HasCallback(funcProvider.abc, funcProvider.xyz);
}

class UnaryFunctionClass {
  void call() {}
}

class MockFunctionProvider extends Mock implements FunctionProvider {}

class MockUnaryFunctionClass extends Mock implements UnaryFunctionClass {}

void main() {
  tearDown(() {
    // In some of the tests that expect an Error to be thrown, Mockito's
    // global state can become invalid. Reset it.
    resetMockitoState();
  });

  test('obj.foo is not equal to obj.foo()', () {
    var funcProvider = new MockFunctionProvider();
    expect(funcProvider.foo, null);
    verify(funcProvider.foo);

    funcProvider.foo();
    verify(funcProvider.foo());
  });

  test('using obj.foo as first class citizen is not possible now', () {
    expectFail('The method \'call\' was called on null', () {
      try {
        var funcProvider = new MockFunctionProvider();
        var hasCallback = toBeTested(funcProvider);
        hasCallback.foo();
        verify(funcProvider.abc());
        hasCallback.bar();
        verify(funcProvider.xyz());
      } catch (_) {
        throw new TestFailure('The method \'call\' was called on null');
      }
    });
  });

  test('to test member function as fisrt class citizen, '
      'need to write more code',
      () {
    var funcProvider = new MockFunctionProvider();
    var abc = new MockUnaryFunctionClass();
    var xyz = new MockUnaryFunctionClass();
    when(funcProvider.abc).thenReturn(abc);
    when(funcProvider.xyz).thenReturn(xyz);
    var hasCallback = toBeTested(funcProvider);
    hasCallback.foo();
    verify(abc.call());
    hasCallback.bar();
    verify(xyz.call());
  });
}

So for member function foo(), if it is called as mockObj.foo (and no when for it), can we return a stub instead of null, so that the test could be easier.

Thanks

Can't catch errors in when mocking Future.error

when returning a Future in a mock, tests will fail if there is any exception handling using async/await.

when(foo.doStuff()).thenReturn(new Future.error('mock error'));

In this case, any unit test using async/await and calling foo.doStuff will fail with "mock error". If async/await is not used, the mock error will be caught and the test will pass.

Heres an example project to help illustrate the problem. It is using the latest version of test and mockito.

@fibulwinter @kevmoo

Make PostExpectation generic.

An error cropping up while migrating tests to Dart 2.0 semantics is having invalid return types for thenAnswer which cause runtime issues.
For example consider the class Foo:

class Foo {
    Future<B> doSomething(A a);
}

Set up the mock for Foo as follows:

MockFoo foo = new MockFoo()
when(foo.doSomething(any(a)).thenAnswer((_) => new B())

The above will currently run without issue in the VM. However, the return type of thenAnswer is invalid as it should be returning Future<B> instead of the raw B value. I believe we can catch these errors through analysis. when uses PostExpectation which can be made generic. This will allow us to enforce the return type of thenAnswer accordingly.

Mock refers to "super", preventing it from being used as a mixin.

By supporting _givenHashCode, Mock refers to super, preventing its use a Mixin.

This means if I want to mock out unimplemented functions in an abstract class, I can't.

Here is an example of some interfaces and abstract implementations:

abstract class Foo {
  String bar();
}

abstract class AbstractFoo implements Foo {
  String bar() => baz();

  String baz();
}

I'd like to just extend AbstractFoo but also use Mock to mock out baz:

abstract class MockMixin {
  // Instead of 'noSuchMethod', so we don't use 'super.noSuchMethod'.
  handleNoSuchMethod(Invocation invocation) { ... }
}

// Test the implementation of 'AbstractFoo', and use stubs for the remaining abstract methods.
class MockFoo extends AbstractFoo with MockMixin {
  noSuchMethod(Invocation invocation) => handleNoSuchMethod(invocation);
}

void main() {
  var foo = new MockFoo();
  when(foo.baz()).thenReturn('baz');
  expect(foo.bar(), 'baz');
}

I think it would be alright if there was a base MockMixin. The current Mock could just extend that for people who would prefer extending it. I'd be OK with writing a PR for this feature, but wanted to suggest it first.

Emulating tearoffs hack doesn't work under Dart 2 semantics

This example is inspired from the comment on the Mock class:

// Real class.
class Cat {
  String getSound(String suffix) => 'Meow$suffix';
}

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

void main() {
  // Create a new mocked Cat at runtime.
  var cat = new MockCat();

  // When 'getSound' is called, return 'Woof'
  when(cat.getSound).thenReturn('Woof');   // <-----

  // Try making a Cat sound...
  print(cat.getSound('foo')); // Prints 'Woof'
}

The indicated line is a little magical. In Dart 1 semantics, the evaluation of cat.getSound tries to call a non-existent getter against cat and winds up in Mock.noSuchMethod. Mock uses this point to store a closure in the _whenCall global which records the method name and arguments.

However, under Dart 2 semantics, since getSound(String _) is part of the interface for MockCat, a forwarder function for getSound is created, which will call Mock.noSuchMethod when invoked. Evaluation of cat.getSound now creates a tearoff of this forwarder. Mock.noSuchMethod isn't called, and the Mock class doesn't get a chance observe the method name and arguments.

please add method docs for typed, not just in the README

The README identifies a few situations in which you might want to use typed,
but nothing currently explains what typed actually is/does.
Please add method docs.

In particular, it seems to me that its purpose in life is to claim to strong mode that it returns whatever
type the call site demands (which of course can only happen if it always returns null... which it does). And the reason we'd want that is basically to tell strong mode to shut up.
It's as if its name really should have been inferTypeFromUsage, although that would be much too long.

Mock functions?

Don't really know how or if this is achievable but it would be great if there's some magical means to create a mock function rather than a mock class that can be passed into things expecting any sort of typedefs or function signatures and then verify its (dynamic) calls and args.

Add async return helper

A lot of testing involves async code and it would be super convenient if you didn't have to create wrapper futures all the time but could instead delegate that to a thenReturnAsync method.
It would basically just be this in PostExpectation

  thenReturnAsync(expected) {
    return _completeWhen((_) => new Future.value(expected));
  }

I can submit a pull request if this sounds reasonable.

Mock methods

I have this example code:

class FooExample {

  int calculate() {
    return myCalc();
  }

  int myCalc() {
    return 6 * 7;
  }
}

And I have following test code:

import 'package:test/test.dart';
import 'package:mockito/mockito.dart';

class MockFooExample extends Mock implements FooExample { }

void main() {

  FooExample ft = spy(new MockFooExample(), new FooExample());

  test('calculate', () {
    when(ft.myCalc()).thenReturn(10);
    expect(ft.myCalc(), 10);
    expect(ft.calculate(), 10);
  });
}

I was expecting the last expect call to return 10, when calling ft.calculate(), but it still return 42.
Am I missing something?

Captures use global vars, causing cross-talk between tests. Need a tearDown function

See the following repro: captures are not hermetic across tests, even though I'm using different mock instances between tests!!!

import 'package:mockito/mockito.dart';
import 'package:test/test.dart';

class Foo {
  foo(x) {}
}
class MockFoo extends Mock implements Foo {}

main() {
  MockFoo foo;
  setUp(() {
    foo = new MockFoo();
    when(foo.foo(captureAny)).thenAnswer((i) => null);
  });

  test('calls', () {
    foo.foo(1);
  });
  test('calls but gets all tests\' captures!', () {
    foo.foo(2);
    expect(verify(foo.foo(any)).captured, [1, 2]);
  });
}

Note that some things do seem to reset captures:

  • the (advertised as testing-only) function resetMockitoState, which we'll be using as a temporary workaround.

  • Any test which call to verify fails to match any invocation:

    test('does not call', () {
      expect(() => verify(foo.foo(any)), throws);
    });

We'd really need something like:

  tearDown(() => mockitoTearDown());

(hit the bug with some real-life tests)

Need a 2.3.0 release with same-semantic wrappers for easy migration to Mockito 3

Today, the ergonomic any can be used in named arguments, but this won't be allowed in Mockito 3.0. Additionally, to use Mockito with DDC ("Dart 2 semantics"), we provide the typed API, that provides an optional named argument.

// MOCKITO 2.x API

// Plain `argThat` and `any` API.
when(mockObj.fn(argThat(contains('text')), foo: any)).thenReturn(0);

// A test run with DDC that could not pass ArgMatcher arguments at runtime.
when(mockObj.fn(typed(any), foo: typed(any, named: 'foo'))).thenReturn(0);

Both of these examples are incompatible with the proposed syntax for Mockito 3.0 (#85), even though we leave typed around for backwards compatibility purposes. They would need to be migrated to:

// MOCKITO 3.X API
// Bare `any` calls are no longer allowed in named arguments.
when(mockObj.fn(argThat(contains('text')), foo: anyNamed('foo'))).thenReturn(0);

// `typed` made available for backward-compatibility...
when(mockObj.fn(typed(any), foo: anyNamed('foo'))).thenReturn(0);
// ... but could be removed: it's the identity function in Mockito 3.0.
when(mockObj.fn(any, foo: anyNamed('foo'))).thenReturn(0);

We should provide a release with forwards-and-backwards-compatible API:

anyNamed(String named) => typed(any, named: named);
captureAnyNamed(String named) => typed(captureAny, named: named);
captureThat(Matcher matcher, {String named}) =>typed(captureThat(matcher), named: named);

With this API, the code could be moved to the MOCKITO 3.X API, and work in both Mockito 2.3+ and Mockito 3.0.

This transition implementation would be replaced with the release of Mockito 3.0:

Null anyNamed(String named) => _registerMatcher(anything, false, named: named);
Null captureAnyNamed(String named) =>
    _registerMatcher(anything, true, named: named);
Null captureThat(Matcher matcher, {String named}) =>
    _registerMatcher(matcher, true, named: named);

Offer an easy way to ignore named parameters

Consider

void a(String b, {String c, String d})

If it was actually invoked with a('b', c: null) or a('b', c: null, d: null) and I only care about 'b', there's no easy way to match that without manually going through every permutation.

Discuss: "No matching calls" when verify() calls should diff...

if there is an equivalent call with non-matching parameters. For example, instead of

No matching calls. All calls: MyService.update(div, {bottom: 5})

Perhaps

No matching calls.

FOUND: MyService.update(div, {bottom: 5})
EXPECTED: MyService.update(div, {bottom: 4})
DIFF:
  bottom was 4 instead of 5

(I'd be happy to help write this, just wanted input first)

@TedSander

Make Mock.realCalls private

This will eliminate any chance of collision with other APIs (a huge issue with mock). You can add a public function to retrieve the value if needed.

More Maintainers

I'm not sure how to give you this message otherwise, but I would be happy to help you maintain this if you are looking for someone to help.

typed() doesn't work well with named parameters

In order to transition to strong mode, we have been converting our when() and verify() calls from
any and captureAny to typed(any) and typed(captureAny)

However this doesn't work well with named parameters where

when(... foo: any) becomes when(... foo: typed(any, named: 'foo'))

Why does typed need to know the name of the parameter?
And the tests will break when the name of the parameter is refactored, which runs counter to one of the main advantages of Mockito compared to other mocking frameworks.

argThat(...) doesn't work in stricter strong mode

... i.e. implicit-casts: false and implicit-dynamic: false.

/// An argument matcher that matches an argument that matches [matcher].
argThat(Matcher matcher) => new ArgMatcher(matcher, false);

But typed expects a ArgMatcher. The above returns dynamic.

Is this fixable? Or should we just basically wait for a future codegen-based mockito?

Add information on how it works

Mockito looks great, but it would be nice if you could add information in the README (or wiki) on how it works.

For example:

// unstubbed methods return null
expect(cat.sound(), nullValue);
// So I'm assuming here that cat.sound() returns `null`

// stubbing - before execution
when(cat.sound()).thenReturn("Purr");
// How does `cat` get configured here? If `cat.sound()` returns
// `null`, I don't see how `when` is able to modify `cat`s behaviour

I can of course (and will) go through the code, to get this information, but it would be nice to have a paragraph about the internals.

Having a class use a mocked version of another one

While learning about flutter testing and trying to write tests for my small application, I couldn't figure out how to mock a class used by another one.

Considering a stateful widget ListScreen and its related ListScreenState, the build methods returns an AppBar with a localized text.

When trying to write a test for this widget

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';

import 'package:grablunch/list.dart' show ListScreen;
import 'package:grablunch/localization.dart' show AppLocalizations;

class MockAppLocalizations extends Mock implements AppLocalizations {}

void main() {
  var localizations = new MockAppLocalizations();
  when(localizations.titleList).thenReturn("titleList");

  testWidgets('my first widget test', (WidgetTester tester) async {
    await tester.pumpWidget(
      new StatefulBuilder(
        builder: (BuildContext context, StateSetter setState) {
          return new ListScreen();
        },
      ),
    );
  });
}

flutter test will fail as follow:

➜ flutter test test/widget_test.dart
00:00 +0: - my first widget test
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
The following NoSuchMethodError was thrown building ListScreen(dirty, state: ListScreenState#496f0):
The getter 'titleList' was called on null.
Receiver: null
Tried calling: titleList

Makes sense since the translation is called out of AppLocalizations.of(context).titleList), and the locales were defined in the root widget, not used for this test (trying to make a widget test, not an integration one).

How would you go about mocking AppLocalizations (or the context) on the ListScreen class?
I tried

var testListScreen = new ListScreen();
testListScreen.context = new Mock();

and to return testListScreen instead, but the context not having a setter, I failed to suceed this way.

Kind of tough to figure things out, lacking some debugging foo up my sleeves. To be honest, I'm not even sure how to test this kind of app that heavily relies on external packages like the firebase ones.
Thanks for your help

Mockito needs significantly more documentation

The dartdocs for mockito leave much to be desired.

Here are some random examples:

  • when has no docs and the library itself has no top-level docs (as far as I can tell these are the two most important things in mockito).

  • One of the first things in the README is:

    Let's verify some behaviour!

    //using mock object
    cat.sound();
    //verify interaction
    verify(cat.sound());

    Once created, mock will remember all interactions. Then you can selectively verify whatever interaction you are interested in.

    I have no idea what any of that means. The word "verify" is never defined. The dartdocs for verify are blank.

  • The README references throwOnMissingStub in a code sample with no prose. That method doesn't seem to exist.

  • The word "answer" appears exactly once in the README, with no commentary, and there's no dartdocs for either PostExpectation.thenAnswer or Answering.

verify(someMethod).called(0) fails with unintuitive error.

I initially thought that verify(something).called(0) would be equivalent to verifyNever. Instead the first fails with a no matching calls error. I am not sure if this is a bug or not, but if called doesn't support values <= 0 then it should throw a better error, perhaps pointing a user to verifyNever.

Minimal example:

import 'package:test/test.dart';
import 'package:mockito/mockito.dart';

void main() {
  group('verify calls', () {
    test('verifyNever passes', () {
      var cat = new MockCat();

      verifyNever(cat.countLives());
    });

    test('verify .called(0) fails', () {
      var cat = new MockCat();

      verify(cat.countLives()).called(0);
    });
  });
}


class Cat {
  int countLives() => 9;
}

class MockCat extends Mock implements Cat {}

add thenThrow to API

This can be done now with thenAnswer, but it's nice for the API (Both Dart's package:mock and Java's mockito have a thenThrow).

Enable travis builds

I've got the scripts in flight (#9) but the repo needs to be enabled. Apparently, this can only done by the repo owner, so it's on you @fibulwinter :)

To do it, visit https://travis-ci.org/ and you should find a slider for dart-mockito. Flick the repository switch on and we should be good to go.

Friendly debug messages when mock methods without expectations set are called.

For example:

//Real class
class Cat {
  String sound() => "Meow";
}

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

//mock creation
var mockCat = new MockCat();

// Would throw exception "NoSuchMethodError: The method 'toUpperCase' was called on null.".
mockCat.sound().toUpperCase();

The actual cause is during the test development, developer forgot to write "when(cat.sound()).thenReturn("Purr");".

The message "'toUpperCase' was called on null" isn't helpful for finding out the casue and a more friendly message could be "'toUpperCase' was called on the return value of 'Cat.sound()', but no exceptions were set on it".

This could be achieved by returning instead of 'null' but a special object carrying the method invocation information for any 'un-expected' methods.

I can come with a design doc if this sounds possible.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.