blob: 1439ed2e5742fe92250df47767a3512807cffe58 [file] [log] [blame]
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:test/test.dart';
import 'example.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;
}
// Fake class
class FakeCat extends Fake implements Cat {
@override
bool? eatFood(String? food, {bool? hungry}) {
print('Fake eat $food');
return true;
}
}
abstract class Callbacks {
Cat findCat(String name);
String? makeSound();
}
@GenerateMocks([
Cat,
Callbacks,
], customMocks: [
MockSpec<Cat>(
as: #MockCatRelaxed,
onMissingStub: OnMissingStub.returnDefault,
),
])
void main() {
late Cat cat;
setUp(() {
// Create mock object.
cat = MockCat();
});
test("Let's verify some behaviour!", () {
// Stub a method before interacting with it.
when(cat.sound()).thenReturn('Meow');
// Interact with the mock object.
cat.sound();
// Verify the interaction.
verify(cat.sound());
});
test('How about some stubbing?', () {
// Unstubbed methods throw MissingStubError.
expect(() => cat.sound(), throwsA(isA<MissingStubError>()));
// Stub a method before interacting with it.
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.
final responses = ['Purr', 'Meow'];
when(cat.sound()).thenAnswer((_) => responses.removeAt(0));
expect(cat.sound(), 'Purr');
expect(cat.sound(), 'Meow');
});
test('Argument matchers', () {
// You can use 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);
cat.hunt('backyard', null);
verify(cat.hunt('backyard', null)); // OK: no arg matchers.
cat.hunt('backyard', null);
verify(cat.hunt(argThat(contains('yard')),
argThat(isNull))); // OK: null is wrapped in an arg matcher.
});
test('Named arguments', () {
// 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);
});
test('Verifying exact number of invocations / at least x / never', () {
when(cat.sound()).thenReturn('Meow');
cat.sound();
cat.sound();
// Exact number of invocations
verify(cat.sound()).called(2);
cat.sound();
cat.sound();
cat.sound();
// Or using matcher
verify(cat.sound()).called(greaterThan(1));
// Or never called
verifyNever(cat.eatFood(any));
});
test('Verification in order', () {
when(cat.sound()).thenReturn('Meow');
when(cat.eatFood(any)).thenReturn(true);
cat.eatFood('Milk');
cat.sound();
cat.eatFood('Fish');
verifyInOrder([cat.eatFood('Milk'), cat.sound(), cat.eatFood('Fish')]);
});
test('Making sure interaction(s) never happened on mock', () {
verifyZeroInteractions(cat);
});
test('Finding redundant invocations', () {
when(cat.sound()).thenReturn('Meow');
cat.sound();
verify(cat.sound());
verifyNoMoreInteractions(cat);
});
test('Capturing arguments for further assertions', () {
when(cat.eatFood(any)).thenReturn(true);
// 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']);
});
test('Waiting for an interaction', () async {
when(cat.eatFood(any)).thenReturn(true);
Future<void> chewHelper(Cat cat) {
return cat.chew();
}
// Waiting for a call.
chewHelper(cat);
await untilCalled(cat.chew()); // This completes when cat.chew() is called.
// Waiting for a call that has already happened.
cat.eatFood('Fish');
await untilCalled(cat.eatFood(any)); // This completes immediately.
});
test('Mocked callbacks', () {
final makeSoundCallback = MockCallbacks().makeSound;
when(makeSoundCallback()).thenReturn('woof');
expect(makeSoundCallback(), 'woof');
final findCatCallback = MockCallbacks().findCat;
final mockCat = MockCat();
when(findCatCallback('Pete')).thenReturn(mockCat);
when(mockCat.sound()).thenReturn('meow');
expect(findCatCallback('Pete').sound(), 'meow');
});
test('Fake class', () {
// Create a new fake Cat at runtime.
final cat = FakeCat();
cat.eatFood('Milk'); // Prints 'Fake eat Milk'.
expect(() => cat.sleep(), throwsUnimplementedError);
});
test('Relaxed mock class', () {
// Create a new mock Cat at runtime.
final cat = MockCatRelaxed();
// You can call it without stubbing.
cat.sleep();
// Non-null properties and function return values
// default to reasonable defaults.
expect(cat.lives, 0);
// Nullable properties and function return values
// default to null unless you stub them.
expect(cat.sound(), null);
expect(cat.eatFood('Milk'), null);
verify(cat.sleep());
});
}