Introduction
Testing is a critical part of building reliable Flutter applications. With the BLoC pattern, you can easily test your business logic in isolation without dealing with UI. This guide covers everything you need to know about testing BLoCs and Cubits – from simple unit tests to advanced scenarios using blocTest, mocking dependencies, and testing error handling.
Why Test BLoCs?
- Isolated Logic – BLoCs contain pure business logic, making them ideal for unit tests.
- Predictable State Transitions – Given an event and initial state, the emitted states should be deterministic.
- Catch Bugs Early – Ensure your state changes behave as expected before they reach the UI.
- Refactor with Confidence – Tests give you safety when changing internal implementations.
- Documentation – Tests serve as living documentation of how your BLoC should behave.
Setting Up Testing Dependencies
Add the required packages to your pubspec.yaml dev dependencies:
Testing a Simple Cubit
Let's start with a simple counter Cubit. The blocTest function from bloc_test is the main tool for testing BLoCs and Cubits.
Testing a Bloc with Events
For BLoCs that use events, the pattern is similar. You provide events to act and expected states to expect.
Testing with Dependencies (Mocking)
Most real‑world BLoCs depend on repositories or services. Use mocktail to mock dependencies and control their behavior. The build function can create the BLoC with mocked dependencies.
Testing State Equality with Equatable
Use Equatable in your state classes so that blocTest can correctly compare expected and actual states. Without proper equality, tests may fail even if the data is the same.
Testing with Streams and emit.forEach
If your BLoC uses emit.forEach, you can test it by providing a controlled stream. Use StreamController to emit values during the test.
Testing BlocObserver
You can test custom BlocObserver by creating a test observer and verifying that methods are called.
Best Practices
- Use
blocTest– It provides a clean, declarative API for testing state emissions. - Mock dependencies with
mocktail– Avoid using real network/database calls in unit tests. - Use
Equatablein states – Simplifies equality comparisons in tests. - Test error cases – Ensure your BLoC handles exceptions and emits error states.
- Keep tests focused – Test one scenario per
blocTest. - Use
setUpandtearDown– Reset mocks and observers between tests. - Avoid
asyncinact–blocTesthandles async automatically; just add events. - Use
skipto ignore initial state – If you don't want to verify the starting state.
Common Mistakes
- ❌ Not mocking dependencies – Tests become slow and unreliable. ✅ Always mock external services.
- ❌ Using
expectinsideact– Causes race conditions; use theexpectparameter ofblocTest. - ❌ Forgetting to register adapters for Hive – Tests will fail when using real Hive. ✅ Use in‑memory boxes or mock Hive.
- ❌ Testing UI together with BLoC – Should be separate; BLoC tests should be pure Dart tests (no
WidgetTester). - ❌ Ignoring state equality – Leads to false negatives.
✅ Implement
==or use Equatable.
Conclusion
Testing BLoCs is straightforward with the bloc_test package. By isolating business logic, mocking dependencies, and using blocTest, you can write comprehensive tests that verify state transitions, error handling, and edge cases. A well‑tested BLoC layer gives you confidence to refactor and add features without breaking existing functionality.