mirror of
https://github.com/dart-lang/sdk
synced 2024-09-15 23:09:48 +00:00
215 lines
7.5 KiB
Markdown
215 lines
7.5 KiB
Markdown
A library for mocking classes and verifying expected interaction with mocks.
|
|
|
|
It is inspired by [Mockito](https://code.google.com/p/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:
|
|
- https://pub.dartlang.org/packages/mockito
|
|
- https://pub.dartlang.org/packages/mock (deprecated)
|
|
|
|
## 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.
|
|
|
|
```dart
|
|
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:
|
|
|
|
```dart
|
|
|
|
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 exactly `n` times.
|
|
- `atLeast(n)` Ensure the function was called `n` or more times.
|
|
- `atMost(n)` Ensure the function was called no more than `n` 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 use `verifyNoMoreInteractions()` instead.
|
|
|
|
#### Configure the mock to return a value
|
|
|
|
Here's how to return a value from `speak()`:
|
|
|
|
```dart
|
|
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:
|
|
|
|
|
|
```dart
|
|
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:
|
|
|
|
```dart
|
|
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()`:
|
|
|
|
```dart
|
|
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:
|
|
|
|
```dart
|
|
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:
|
|
```dart
|
|
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.
|
|
|
|
```dart
|
|
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`.
|
|
|
|
```dart
|
|
void doSomething(String myfunc()) {}
|
|
|
|
void main() {
|
|
final dog = new MockDog();
|
|
doSomething(dog.speak);
|
|
verifyNoMoreInteractions(dog);
|
|
}
|
|
```
|
|
|
|
The solution is as follows:
|
|
|
|
```dart
|
|
void doSomething(String myfunc()) {}
|
|
|
|
void main() {
|
|
final dog = new MockDog();
|
|
doSomething(() => dog.speak());
|
|
verifyNoMoreInteractions(dog);
|
|
}
|
|
```
|
|
|
|
## More Information
|
|
|
|
For additional examples, see the [unit tests](https://github.com/dart-lang/sdk/blob/master/pkg/typed_mock/test/typed_mock_test.dart).
|