Skip to content

Latest commit

 

History

History
205 lines (158 loc) · 8.39 KB

FAQ.md

File metadata and controls

205 lines (158 loc) · 8.39 KB

Frequently asked questions

How do I mock a static method, constructor, or top-level function?

Mockito provides its stubbing and verification features by overriding class instance methods. Since there is no mechanism for overriding static methods, constructors, or top-level functions, mockito cannot mock them. They are what they are.

One idea to consider is "Do I need to use mocks to test this code?" For example, the test_descriptor package allows testing file system concepts using real files, and the test_process package supports testing subprocesses using real subprocesses. dart:io also includes an IOOverrides class and a runWithIOOverrides function that can be used to mock out the filesystem.

If mocking is still desired, the underlying code may be refactored in order to enable mocking. One way to get around un-mockable constructors is to change the function in which the constructor is being called. Instead of constructing an object, accept one.

// BEFORE:
void f() {
  var foo = Foo();
  // ...
}

// AFTER
void f(Foo foo) {
  // ...
}

In tests, you can declare a MockFoo class which implements Foo, and pass such an object to f.

You can also refactor code which makes much use of such constructors or static methods to use a wrapper system. For example, instead of calling Directory.current or new File throughout your code, use the file package. You can start with a FileSystem object (a LocalFileSystem for production and a MemoryFileSystem for tests), and use its wrapper methods (currentDirectory replaces Directory.current, file() replaces File()). Another example of this pattern is the io package and its ProcessManager class.

How do I mock an extension method?

If there is no way to override some kind of function, then mockito cannot mock it. See the above answer for further explanation, and alternatives.

Why can a method call not be verified multiple times?

When mockito verifies a method call (via verify or verifyInOrder), it marks the call as "verified", which excludes the call from further verifications. For example:

cat.eatFood("fish");
verify(cat.eatFood("fish"));  // This call succeeds.
verify(cat.eatFood(any));  // This call fails.

In order to make multiple reasonings about a call, for example to assert on the arguments, make one verification call, and save the captured arguments:

cat.hunt("home", "birds");
var captured = verify(cat.hunt(captureAny, captureAny)).captured.single;
expect(captured[0], equals("home"));
expect(captured[1], equals("birds"));

If you need to verify the number of types a method was called, and capture the arguments, save the verification object:

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

How does mockito work?

The basics of the Mock class are nothing special: It uses noSuchMethod to catch all method invocations, and returns the value that you have configured beforehand with when() calls.

The implementation of when() is a bit more tricky. Take this example:

// Unstubbed methods return null:
expect(cat.sound(), nullValue);

// Stubbing - before execution:
when(cat.sound()).thenReturn("Purr");

Since cat.sound() returns null, how can the when() call configure it?

It works, because when is not a function, but a top level getter that returns a function. Before returning the function, it sets a flag (_whenInProgress), so that all Mock objects know to return a "matcher" (internally _WhenCall) instead of the expected value. As soon as the function has been invoked _whenInProgress is set back to false and Mock objects behave as normal.

Argument matchers work by storing the wrapped arguments, one after another, until the when (or verify) call gathers everything that has been stored, and creates an InvocationMatcher with the arguments. This is a simple process for positional arguments: the order in which the arguments has been stored should be preserved for matching an invocation. Named arguments are trickier: their evaluation order is not specified, so if Mockito blindly stored them in the order of their evaluation, it wouldn't be able to match up each argument matcher with the correct name. This is why each named argument matcher must repeat its own name. foo: anyNamed('foo') tells Mockito to store an argument matcher for an invocation under the name 'foo'.

Be careful never to write when; (without the function call) anywhere. This would set _whenInProgress to true, and the next mock invocation will return an unexpected value.

The same goes for "chaining" mock objects in a test call. This will fail:

var mockUtils = MockUtils();
var mockStringUtils = MockStringUtils();

// Setting up mockUtils.stringUtils to return a mock StringUtils implementation
when(mockUtils.stringUtils).thenReturn(mockStringUtils);

// Some tests

// FAILS!
verify(mockUtils.stringUtils.uppercase()).called(1);
// Instead use this:
verify(mockStringUtils.uppercase()).called(1);

This fails, because verify sets an internal flag, so mock objects don't return their mocked values anymore but their matchers. So mockUtils.stringUtils will not return the mocked stringUtils object you put inside.

You can look at the when and Mock.noSuchMethod implementations to see how it's done. It's very straightforward.

How can I customize where Mockito outputs its mocks?

Mockito supports configuration of outputs by the configuration provided by the build package by creating (if it doesn't exist already) the build.yaml at the root folder of the project.

It uses the build_extensions option, which can be used to alter not only the output directory but you can also do other filename manipulation, eg.: append/prepend strings to the filename or add another extension to the filename.

To use build_extensions you can use ^ on the input string to match on the project root, and {{}} to capture the remaining path/filename.

You can also have multiple build_extensions options, but they can't conflict with each other. For consistency, the output pattern must always end with .mocks.dart and the input pattern must always end with .dart.

If you specify a build extension, you MUST ensure that your patterns cover all input files that you want generate mocks from. Failing to do so will lead to the unmatched file from not being generated at all.

targets:
  $default:
    builders:
      mockito|mockBuilder:
        generate_for:
        options:
          # build_extensions takes a source pattern and if it matches it will transform the output
          # to your desired path. The default behaviour is to the .mocks.dart file to be in the same
          # directory as the source .dart file. As seen below this is customizable, but the generated
          # file must always end in `.mocks.dart`. 
          build_extensions:
            '^tests/{{}}.dart' : 'tests/mocks/{{}}.mocks.dart' 
            '^integration-tests/{{}}.dart' : 'integration-tests/{{}}.mocks.dart' 

Also, you can also check out the example configuration in the Mockito repository.