34f1501149
BUG= R=pquitslund@google.com Review URL: https://codereview.chromium.org/2408713004 . |
||
---|---|---|
.. | ||
lib | ||
test | ||
BUILD.gn | ||
LICENSE | ||
pubspec.yaml | ||
README.md |
A library for mocking classes and verifying expected interaction with mocks.
It is inspired by Mockito.
Features of this package:
- Code-completion, static validation, search and refactoring all work properly with mocks.
- Much better error messages for unit testing.
- Works with concrete and abstract classes.
- Does not use mirrors.
- No dependent packages.
Other Mock libraries for Dart:
Tutorial
Let's take the simple case of making sure that a method is called. The first step is to create a mock of the object, as shown below. One nice feature of Dart is that all classes automatically define interfaces, so you don't need to separately define the interface.
import 'package:typed_mock/typed_mock.dart';
class Dog {
String speak() => "Woof!";
}
class MockDog extends TypedMock implements Dog {
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
All of the magic happens because of the noSuchMethod() function. None of the functions for Animal are actually defined because we used implements
instead of extends
. Therefore, all calls to this object will end up in noSuchMethod()
.
Verify a function is called
Here's the code to verify that the function is called:
void main() {
final dog = new MockDog();
verifyZeroInteractions(dog);
dog.speak();
verify(dog.speak()).once();
verifyNoMoreInteractions(dog);
}
One of the interesting features of typed_mock is that it internally tracks all calls to each mock object, then tracks which calls have been matched with a verify() call and which not. Therefore, typed_mock is able to detect unexpected calls, even if those calls are made to methods that didn't exist when the test was written. This can be a good incentive to update your tests whenever you change a class.
After creating the MockAnimal
object, we call verifyZeroInteractions()
to make sure that the object starts in a clean state. Next we call the speak()
method, then prove that the speak function was actually called with verify().once()
.
There are several other functions for verifying calls that can be used instead of once()
:
atLeastOnce()
Ensure the function was called one or more times.times(n)
Ensure the function was called exactlyn
times.atLeast(n)
Ensure the function was calledn
or more times.atMost(n)
Ensure the function was called no more thann
times.any()
Mark the function call as verified if it was called, but don't fail if it wasn't called.never()
Ensure the function was never called. It's often better to useverifyNoMoreInteractions()
instead.
Configure the mock to return a value
Here's how to return a value from speak()
:
void main() {
final dog = new MockDog();
when(dog.speak()).thenReturn("woof");
final s = dog.speak();
print("$s");
verify(dog.speak()).once();
verifyNoMoreInteractions(dog);
}
What if speak()
took the name of an animal as a parameter? When typed_mock tracks a function call, the call tracking is based on the function name and the parameters. For example:
import 'package:typed_mock/typed_mock.dart';
abstract class Animal {
String speak();
}
class MockAnimal extends TypedMock implements Animal {
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void main() {
final animal = new MockAnimal();
when(animal.speak("dog")).thenReturn("woof");
final s = animal.speak();
print("$s");
verify(animal.speak("dog")).once();
}
Note that you can reset the call and verify tracking using resetInteractions()
. However, there is no way to reset the when()
calls. Create a new mock instead.
You can define different results based on the value of the parameter. Notice that the calls to verify()
explicitly states the parameter value:
void main() {
final animal = new MockAnimal();
when(animal.speak("cat")).thenReturn("meow");
when(animal.speak("dog")).thenReturn("woof");
final s = animal.speak("cat"); // Prints: meow
verify(animal.speak("cat")).once();
verify(animal.speak("dog")).never();
}
Match any value for a parameter
Sometimes you don't care about the exact value of the parameter. That's when anyString
is used, along with its siblings anyInt
, anyBool
and anyObject
.
The value anyString
is a matcher that matches any String value. For example, here's how to use anyString
in a call to when()
:
void main() {
final animal = new MockAnimal();
when(animal.speak(anyString)).thenReturn("meow");
final s1 = animal.speak("cat");
final s2 = animal.speak("dog");
print("$s1 $s2"); // Prints: meow meow
verify(animal.speak(anyString)).times(2);
}
You can also use anyString
in verify()
calls, even if the when()
calls use exact values. For example:
void main() {
final animal = new MockAnimal();
when(animal.speak("cat")).thenReturn("meow");
when(animal.speak("dog")).thenReturn("woof");
var s
s = animal.speak("cat");
s = animal.speak("cat");
s = animal.speak("dog");
verify(animal.speak(anyString)).times(3);
}
You can use anyString
as the parameter for calculated values:
when(animal.speak(anyString)).thenInvoke((String s) => 'The $s speaks!');
In addition to thenReturn()
and thenInvoke()
, typed_mock supports thenReturnList()
and thenThrow()
. See the link at the end of this document for examples.
Mocking operator[] and operator[]=
The typed_mock package is able to track set and get access with operators []=
and []
, respectively. There's nothing special about these operators - they are just functions with non-alphanumeric names that takes two or one parameters. As with other functions, typed_mock tracks both the index and the value as needed. Note the syntax to verify that a particular array element was assigned a particular value. The act of assigning true is tracked separately from the act of assigning false. The syntax is straightforward.
import 'package:typed_mock/typed_mock.dart';
abstract class Tracker {
operator [](int index);
operator []=(int index, bool b);
}
class MockTracker extends TypedMock implements Tracker {
noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation);
}
void main() {
final tracker = new MockTracker();
tracker[2] = true;
when(tracker[3]).thenReturn(false);
when(tracker[4]).thenReturn(true);
bool x = tracker[3];
bool y = tracker[4];
print("$x $y");
verify(tracker[1] = true).never();
verify(tracker[2] = false).never();
verify(tracker[2] = true).once();
verify(tracker[3]).once();
verify(tracker[4]).once();
verify(tracker[5]).never();
}
Passing mocks as closures
Passing a mock as a function parameter may not behave as you expect because a hidden function is called in the mock to get the closure. The solution is to wrap the call in a separate closure. For example, the call to verifyNoMoreInteractions()
fails because the reference to dog.speak
caused a hidden function to be called in MockDog
.
void doSomething(String myfunc()) {}
void main() {
final dog = new MockDog();
doSomething(dog.speak);
verifyNoMoreInteractions(dog);
}
The solution is as follows:
void doSomething(String myfunc()) {}
void main() {
final dog = new MockDog();
doSomething(() => dog.speak());
verifyNoMoreInteractions(dog);
}
More Information
For additional examples, see the unit tests.