Using Mocking and Stubbing Libraries in Flutter

In Flutter development, testing plays a crucial role in ensuring the reliability and robustness of your applications. One key aspect of effective testing is the ability to isolate units of code and mock or stub dependencies. This allows you to verify that your code interacts correctly with external services and dependencies without actually invoking those services during testing. In Flutter, mocking and stubbing are essential techniques for writing efficient and maintainable tests.

What are Mocking and Stubbing?

Mocking is the process of creating simulated objects (mocks) that mimic the behavior of real dependencies. These mocks allow you to define expected interactions, verify that methods were called correctly, and control the values returned.
Stubbing is a technique where you replace real dependencies with simplified versions (stubs) that provide predefined responses. Stubs are useful when you need to ensure a consistent and predictable state for your tests.

Why Use Mocking and Stubbing?

  • Isolation: Test units of code in isolation from external dependencies.
  • Speed: Execute tests faster by avoiding real network requests or database queries.
  • Predictability: Ensure consistent test results by controlling the behavior of dependencies.
  • Verification: Confirm that interactions with dependencies occur as expected.

Popular Mocking and Stubbing Libraries in Flutter

Flutter offers several excellent libraries for mocking and stubbing:

  • Mockito: A widely used mocking framework that provides powerful capabilities for creating mocks and verifying interactions.
  • Mocktail: A Flutter-specific library for creating mocks with minimal boilerplate.
  • Fake: A simple and lightweight library for creating stubs with basic functionality.

Using Mockito in Flutter

Mockito is a powerful framework that supports a wide range of mocking features.

Step 1: Add Dependencies

Add the necessary dependencies to your pubspec.yaml file:

dev_dependencies:
  mockito: ^5.1.0
  build_runner: ^2.1.0

Then, run flutter pub get to install the dependencies.

Step 2: Create a Mock

Define a mock class using the @GenerateNiceMocks annotation:

import 'package:mockito/annotations.dart';
import 'package:your_project/data/api_service.dart';

@GenerateNiceMocks([ApiService])
void main() {}

Run flutter pub run build_runner build to generate the mock class (e.g., api_service.mocks.dart).

Step 3: Implement a Test

Write your test case using the generated mock:

import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/mockito.dart';
import 'package:your_project/data/api_service.dart';
import 'api_service.mocks.dart';

void main() {
  late MockApiService mockApiService;
  late MyService myService;

  setUp(() {
    mockApiService = MockApiService();
    myService = MyService(apiService: mockApiService);
  });

  test('fetchData should return a list of items', () async {
    // Arrange
    when(mockApiService.fetchData()).thenAnswer((_) async => ['item1', 'item2']);

    // Act
    final result = await myService.fetchData();

    // Assert
    expect(result, ['item1', 'item2']);
    verify(mockApiService.fetchData()).called(1);
  });
}

In this example:

  • MockApiService is a generated mock class.
  • setUp initializes the mock and injects it into the service.
  • when defines the behavior of the mock’s fetchData method.
  • verify confirms that the method was called once.

Using Mocktail in Flutter

Mocktail is designed to be straightforward and requires less boilerplate than Mockito.

Step 1: Add Dependencies

Add the necessary dependencies to your pubspec.yaml file:

dev_dependencies:
  mocktail: ^0.3.0

Run flutter pub get to install the dependencies.

Step 2: Create a Mock Class

Define a mock class by extending Mock and implementing the interface or class you want to mock:

import 'package:mocktail/mocktail.dart';
import 'package:your_project/data/api_service.dart';

class MockApiService extends Mock implements ApiService {}

Step 3: Implement a Test

Write your test case using the mock:

import 'package:flutter_test/flutter_test.dart';
import 'package:mocktail/mocktail.dart';
import 'package:your_project/data/api_service.dart';
import 'package:your_project/service/my_service.dart';

void main() {
  late MockApiService mockApiService;
  late MyService myService;

  setUp(() {
    mockApiService = MockApiService();
    myService = MyService(apiService: mockApiService);
  });

  test('fetchData should return a list of items', () async {
    // Arrange
    when(() => mockApiService.fetchData()).thenAnswer((_) async => ['item1', 'item2']);

    // Act
    final result = await myService.fetchData();

    // Assert
    expect(result, ['item1', 'item2']);
    verify(() => mockApiService.fetchData()).called(1);
  });
}

With Mocktail, the when and verify methods are more concise and Flutter-specific.

Using Fake in Flutter

The fake package offers an approach where you create simplified class implementations for test purposes. It is lighter than using traditional mock libraries.

Step 1: Add Dependencies

Add the dependency to your pubspec.yaml file:


dev_dependencies:
  fake_async: ^0.12.0+3 # example usage

Run flutter pub get to install the dependency.

Step 2: Implement a Test

Define a simple class, rather than mocks:


import 'package:your_project/data/api_service.dart';

class FakeApiService implements ApiService {
  @override
  Future<List> fetchData() async {
    return ['fakeItem1', 'fakeItem2'];
  }
}

Implement your unit tests:


import 'package:flutter_test/flutter_test.dart';
import 'package:your_project/service/my_service.dart';
import 'package:your_project/data/api_service.dart';
import 'fake_api_service.dart';

void main() {
  late FakeApiService fakeApiService;
  late MyService myService;

  setUp(() {
    fakeApiService = FakeApiService();
    myService = MyService(apiService: fakeApiService);
  });

  test('fetchData should return a list of fake items', () async {
    final result = await myService.fetchData();
    expect(result, ['fakeItem1', 'fakeItem2']);
  });
}

With the fake objects, tests depend on implementations you make that follow base contracts instead of using dynamic mocks and generation.

This provides speed when writing simpler tests and using basic class replacement practices, especially for methods.

Best Practices for Mocking and Stubbing

  • Keep Tests Readable: Use clear and concise mocking definitions to make your tests easy to understand.
  • Avoid Over-Mocking: Only mock dependencies that are external to the unit being tested.
  • Verify Important Interactions: Focus on verifying the most critical interactions with dependencies.
  • Maintain Test Integrity: Ensure that mocks accurately represent the behavior of real dependencies to prevent false positives.

Conclusion

Mocking and stubbing are essential techniques for writing robust and reliable tests in Flutter. By isolating units of code and simulating dependencies, you can improve the speed, predictability, and maintainability of your tests. Choose the mocking or stubbing library that best fits your project’s requirements and follow best practices to ensure your tests are effective and valuable. Whether you opt for Mockito’s power, Mocktail’s simplicity, or the lightweight fakes, mastering these techniques will significantly enhance your Flutter testing workflow.