Is it possible to mock or fake a class that needs to be extended with some other class with mockito
plugin in Dart?
I have the following example of inheritance:
abstract class Animal {
Animal(this.emoji);
final String emoji;
String greet();
}
class Dog extends Animal {
Dog(super.emoji);
@override
String greet() {
return '$emoji: Woof!';
}
}
class Poodle extends Dog {
Poodle(super.emoji);
}
class FakePoodle extends Fake implements Poodle {
// Raises a runtime error:
// Runtime error: No associated positional super constructor parameter.
FakePoodle(super.emoji);
}
I want to mock the class FakePoodle
that would still inherit all the properties of the class Dog
, using a constructor and make it mock with mockito
at the same time.
Here are my tests:
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
void main() {
group('Dog tests', () {
test('should return a dog greet', () {
expect(Dog('🐶').greet(), '🐶: Woof!');
});
test('Poodle should return a poodle greet', () {
expect(Poodle('🐩').greet(), '🐩: Woof!');
});
test('Fake Poodle should greet only once', () {
final FakePoodle fakePoodle = FakePoodle('🐕');
fakePoodle.greet();
verify(fakePoodle.greet());
});
});
}
I found the following FAQ section from mockito
plugin:~
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.
Is there a way I can write integration tests like this in a clean manner?
First, a fake is different from a mock. Mocks are used to verify behavior. (Specifically, they're used to verify the behavior of how other objects interact with the mocked object. The mocked object is not the one being tested.) You cannot use verify
on a Fake
.
If you want FakePoodle
to be a Fake
but to reuse some of its implementation from Poodle
, then you can do:
class FakePoodle extends Fake implements Poodle {
Poodle _realPoodle;
FakePoodle(String emoji) : _realPoodle(emoji);
@override
String greet() => _realPoodle.greet();
}
but it's unclear why you'd want to do that instead of just using an actual Poodle
instance (unless there are other methods on Poodle
that you want to have throw UnimplementedError
s if invoked).
You probably want a mock instead because you want to use verify
. However, mocks shouldn't have implementations; again, they're used to verify the behavior of some other object being tested, so the implementation of the mocked object usually shouldn't matter. Wanting to have a mock inherit some of its implementation from a base class conflicts with that philosophy.
But if you still want a mock but still want to reuse implementation (maybe that implementation is particularly complicated and determining a precomputed stubbed response is non-trivial, or maybe the implementation has important side-effects), then you could make a Mock
wrapper with stubs that call into a real object:
test('Fake Poodle should greet only once', () {
var poodle = Poodle('🐕');
var mockPoodle = MockPoodle();
when(mockPoodle.greet()).thenAnswer((_) => poodle.greet());
mockPoodle.greet();
verify(mockPoodle.greet());
});
Finally, I'll point out that you don't have to use Mock
or Fake
if they don't fit what you want. That is, you also could manually do:
class TrackedPoodle extends Poodle {
int greetCallCount = 0;
TrackedPoodle(super.emoji);
@override
String greet() {
greetCallCount += 1;
return super.greet();
}
}
As an aside, your other terminology is all over the place. "Extension methods" are completely unrelated. Additionally, "integration tests" are holistic tests; they usually shouldn't be using mocks or fakes. Mocks and fakes typically are used to test parts of a program in isolation.