Search code examples
flutterdartinheritancemockitointegration-testing

Mocking an extended class with Dart Mockito?


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?


Solution

  • 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 UnimplementedErrors 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.