Flutter Unit Testing with Mockito: Complete Guide with Examples
Learn how to mock dependencies, test business logic, verify API calls, and write reliable Flutter unit tests using Dart and Mockito.
Category: Flutter Development | Reading time: 10–15 minutes
Writing tests is an important part of building reliable Flutter applications. Unit tests help developers find bugs early, validate business logic, and make changes with greater confidence.
In this tutorial, we will learn how to write unit tests in Flutter using Mockito. We will start with a simple example and gradually test asynchronous methods, repositories, successful responses, and errors.
- The difference between unit, widget, and integration tests.
- How Mockito mocks dependencies.
- How to use when(), thenReturn(), thenAnswer(), and verify().
- How to test asynchronous repository methods.
- How to test success and failure scenarios.
- How to run tests and troubleshoot common errors.
Table of Contents
- Why unit testing matters
- Types of Flutter tests
- Set up Mockito
- Understand the testing architecture
- Write your first unit test
- Mock dependencies with Mockito
- Test asynchronous methods
- Test a repository
- Test errors and edge cases
- Run and debug tests
- Best practices
- Frequently asked questions
1. Why Is Unit Testing Important in Flutter?
A unit test checks a small, isolated piece of code, such as a function, class, or business rule. It verifies that the code behaves as expected for specific inputs.
Find bugs early
Catch logic problems before they reach users.
Safer refactoring
Change implementations while checking existing behavior.
Fast feedback
Run focused tests without launching the complete app.
For example, suppose your application calculates a discount, validates a payment amount, or decides whether a user is eligible for a feature. Unit tests can verify those rules without requiring a real server or database.
2. Types of Tests in Flutter
Functions and business logic
UI and interactions
Complete application flows
Unit tests focus on isolated logic. Widget tests verify that widgets render and respond correctly. Integration tests validate interactions across larger parts of an application, potentially running on a device or emulator.
Mockito is particularly useful for unit tests because it allows you to replace dependencies with controlled test doubles.
3. Set Up Mockito in Your Flutter Project
Create a Flutter project if you do not already have one:
flutter create flutter_mockito_demo
cd flutter_mockito_demo
Add the testing dependencies:
flutter pub add --dev mockito
flutter pub add --dev build_runner
Flutter projects already include the flutter_test package in their development dependencies by default.
Mockito generates mock classes for dependencies. We will use the @GenerateMocks annotation and build_runner to generate a mock class from an abstract interface.
Suggested project structure
lib/
services/
calculator_service.dart
repositories/
user_repository.dart
test/
calculator_service_test.dart
user_repository_test.dart
4. Understand How Mockito Works
Imagine that a class depends on another class. Instead of using the real dependency during a test, we provide a mock that returns predefined results.
This approach keeps unit tests predictable. You do not need a live backend, production credentials, or an internet connection to test the business logic that depends on a mock.
5. Write Your First Flutter Unit Test
Let's begin with a simple calculator service. This demonstrates the basic structure of a test before introducing mocks.
Create lib/services/calculator_service.dart:
class CalculatorService {
int add(int a, int b) {
return a + b;
}
int multiply(int a, int b) {
return a * b;
}
}
Create test/calculator_service_test.dart:
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter_mockito_demo/services/calculator_service.dart';
void main() {
group('CalculatorService', () {
late CalculatorService calculator;
setUp(() {
calculator = CalculatorService();
});
test('adds two numbers', () {
expect(calculator.add(2, 3), 5);
});
test('multiplies two numbers', () {
expect(calculator.multiply(4, 3), 12);
});
test('handles negative numbers', () {
expect(calculator.add(-2, 5), 3);
});
});
}
Replace flutter_mockito_demo in the import with your actual package name if it differs.
Understanding the test functions
group()organizes related tests.setUp()runs before each test in the group.test()defines an individual test case.expect()compares the actual result with the expected value.
Notice that this first example does not need Mockito. Mocking becomes valuable when a class depends on another service, repository, database, or network client.
6. Mock Dependencies with Mockito
Suppose a user service depends on a repository. We want to test the service without accessing a real database or API.
First, create lib/repositories/user_repository.dart:
abstract class UserRepository {
Future<String> getUserName(int id);
}
class UserService {
UserService(this.repository);
final UserRepository repository;
Future<String> getWelcomeMessage(int id) async {
final name = await repository.getUserName(id);
return 'Welcome, $name!';
}
}
Now create test/user_repository_test.dart:
import 'package:flutter_test/flutter_test.dart';
import 'package:mockito/annotations.dart';
import 'package:mockito/mockito.dart';
import 'package:flutter_mockito_demo/repositories/user_repository.dart';
import 'user_repository_test.mocks.dart';
@GenerateMocks([UserRepository])
void main() {
late MockUserRepository mockRepository;
late UserService userService;
setUp(() {
mockRepository = MockUserRepository();
userService = UserService(mockRepository);
});
test('returns a welcome message', () async {
when(mockRepository.getUserName(1))
.thenAnswer((_) async => 'Alex');
final result = await userService.getWelcomeMessage(1);
expect(result, 'Welcome, Alex!');
verify(mockRepository.getUserName(1)).called(1);
});
}
Generate the mock class with:
dart run build_runner build --delete-conflicting-outputs
Build_runner generates user_repository_test.mocks.dart. This generated file should not be edited manually.
when()defines the expected behavior of a mock.thenAnswer()provides a response, including an asynchronous response.verify()checks whether a method was called.called(1)asserts that the method was called once.
7. Test Asynchronous Methods
Flutter applications frequently use asynchronous operations for APIs, local storage, and database queries. Mockito can provide controlled Future responses for these methods.
For example, a successful asynchronous mock can be configured as follows:
when(mockRepository.getUserName(2))
.thenAnswer((_) async => 'Sam');
final result = await userService.getWelcomeMessage(2);
expect(result, 'Welcome, Sam!');
To simulate a failure, use a failed Future:
when(mockRepository.getUserName(3))
.thenAnswer(
(_) async => Future<String>.error(
Exception('Server unavailable'),
),
);
Because UserService currently allows exceptions to propagate, the caller will receive the error. In a real application, you might catch expected failures and map them into a domain-specific exception or result type.
8. Test a Repository with Success and Failure Scenarios
A repository often acts as the boundary between business logic and data sources. Good tests should cover both successful results and expected failure behavior.
For the repository in this example, useful cases include:
Keep each test focused on one behavior. Avoid checking implementation details that do not matter to the class's public contract.
9. Test Error Handling and Edge Cases
Testing only successful scenarios can leave important bugs undiscovered. Consider the following additional cases in your Flutter project:
- Invalid user IDs.
- Empty or missing API data.
- Network timeouts and server errors.
- Unauthorized API responses.
- Unexpected JSON values.
- Repeated calls and dependency interactions.
For example, if your service is expected to propagate a repository exception, you can test it with:
test('propagates repository errors', () async {
when(mockRepository.getUserName(9))
.thenAnswer(
(_) async => Future<String>.error(
Exception('User not found'),
),
);
await expectLater(
userService.getWelcomeMessage(9),
throwsA(isA<Exception>()),
);
});
This test checks the current behavior of UserService. If you later change the service to catch and transform exceptions, update the test to match the intended public behavior.
10. Run and Debug Flutter Unit Tests
Run all tests from the project root:
flutter test
Run one specific test file:
flutter test test/calculator_service_test.dart
Regenerate mocks after changing an annotated interface:
dart run build_runner build --delete-conflicting-outputs
Common problems and solutions
| Problem | Solution |
|---|---|
| Mock class not found | Run build_runner and check the generated file import. |
| Unexpected null result | Stub the relevant method before invoking the class under test. |
| Async test fails | Await the Future or use expectLater for asynchronous assertions. |
| Mockito cannot mock a dependency | Check the annotation, generated code, dependency type, and current Mockito documentation. |
| Test depends on the internet | Mock the API boundary for unit tests and reserve live-server checks for integration tests. |
11. Best Practices for Flutter Unit Testing
- Keep tests independent. One test should not rely on another test's state.
- Use descriptive names. Explain the condition and expected result in the test name.
- Mock external dependencies. Replace network clients, databases, and other external services when testing isolated business logic.
- Cover failures as well as success. Include invalid inputs and expected exceptions.
- Test observable behavior. Focus on results and important interactions rather than private implementation details.
- Regenerate generated mocks when necessary. Keep generated files synchronized with their source interfaces.
- Run tests in CI. Use automated checks to catch regressions before merging code.
12. Frequently Asked Questions
What is Mockito in Flutter?
Mockito is a Dart mocking library that lets developers create mock dependencies, define their responses, and verify interactions during tests.
Do I need Mockito for every unit test?
No. Simple functions and self-contained classes can be tested directly. Mockito is useful when you need to isolate a class from its dependencies.
What is the difference between when() and verify()?
when() configures a mock's behavior. verify() checks whether a method was called as expected.
What is the difference between thenReturn() and thenAnswer()?
thenReturn() provides a predefined return value. thenAnswer() computes a response from an invocation and is especially useful for asynchronous methods and invocation-specific behavior.
Can Mockito test REST API calls?
Yes. You can mock a repository or API client to test how your business logic responds to successful responses and failures. Separate integration tests can verify the real network configuration and backend contract.
Should I use Mockito or mocktail?
Both are popular Dart mocking options. Mockito uses generated mocks for many common scenarios, while mocktail supports a different, runtime-oriented mocking approach. Choose the library that best fits your team's workflow and project requirements.
Conclusion
Flutter unit testing with Mockito helps developers verify business logic independently from databases, APIs, and other dependencies. By combining descriptive test cases, generated mocks, asynchronous testing, and failure scenarios, you can build a more reliable and maintainable application.
Start with simple unit tests, then introduce mocks when you need to isolate dependencies. As your project grows, integrate your tests into CI/CD so problems are caught early.
Keep learning Flutter
Explore the official testing guides and package documentation to improve your testing workflow.
Flutter Testing DocumentationOfficial References
- Flutter testing overview
- Mockito package documentation
- Build Runner documentation
- Flutter test API reference
Code examples are educational. Verify package versions, generated mock output, and imports against your project's current Dart and Flutter SDK.
Comments
Post a Comment