Skip to main content

Writing Unit Tests

1. Understanding Test Types

Type Description Use Case Example
Widget Tests Test UI components and their interactions. Verify widget rendering, tap events, and state changes.
testWidgets('Counter increments', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  expect(find.text('0'), findsOneWidget);
  await tester.tap(find.byIcon(Icons.add));
  await tester.pump();
  expect(find.text('1'), findsOneWidget);
});
Integration Tests Test app flows across multiple screens or modules. Validate end-to-end user journeys and data flow.
testWidgets('Login flow', (WidgetTester tester) async {
  await tester.pumpWidget(LoginScreen());
  await tester.enterText(find.byType(TextField), 'user@example.com');
  await tester.tap(find.byType(ElevatedButton));
  await tester.pumpAndSettle();
  expect(find.text('Welcome'), findsOneWidget);
});
Unit Tests Test individual functions or classes in isolation. Validate logic, calculations, and business rules.
test('calculateTotal returns correct value', () {
  final total = calculateTotal([10, 20, 30]);
  expect(total, 60);
});

2. Writing Unit Tests

Example: Basic Unit Test

import 'package:flutter_test/flutter_test.dart';

void main() {
  test('adds one to input values', () {
    final calculator = Calculator();
    expect(calculator.add(2), equals(3));
  });
}
Feature Description Notes
test() test(String description, void Function() testFn) Defines a single unit test case
setUp() setUp(void Function() setupFn) Runs before each test in the group
tearDown() tearDown(void Function() teardownFn) Runs after each test in the group
Note: Unit tests in Flutter use the flutter_test package and run on the Dart VM.

Example: Testing Widget

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart';

void main() {
  testWidgets('Counter increments smoke test', (WidgetTester tester) async {
    await tester.pumpWidget(MaterialApp(home: Text('Hello')));
    expect(find.text('Hello'), findsOneWidget);
  });
}
Method Description Usage
testWidgets() Runs widget tests Use for UI component testing
pumpWidget() Builds and mounts a widget Required before finding widgets
find.text() Finds widgets by text content Use for verifying UI elements

Test Types in Flutter

  • Unit Tests: Test individual functions or classes in isolation
  • Widget Tests: Test UI components and their interactions
  • Integration Tests: Test full app flows or multiple components together

3. Using test() Function

Parameter Type Description
name String Name of the test case, used for reporting and debugging.
testFn VoidCallback The function containing the test logic to execute.
skip bool If true, the test is skipped during execution.
timeout Duration Timeout duration for the test execution.

Example: Basic test() usage

import 'package:flutter_test/flutter_test.dart';

void main() {
  test('should return true when adding two positive numbers', () {
    final result = 2 + 2;
    expect(result, equals(4));
  });
}

Example: Test with timeout

test(
  'should complete within 5 seconds',
  () {
    // Simulate a long-running test
    expect(true, isTrue);
  },
  timeout: const Timeout(Duration(seconds: 5)),
);

4. Using expect() Assertions

Assertion Description Example
expect(value, message) Checks that the value is truthy or throws an error with the given message expect(result, isNotNull);
expect(value).toEqual(expected) Checks that the value equals the expected value expect(person.name, equals('John'));
expect(value).toBe(expected) Checks that the value is identical to the expected value (same object reference) expect(person, same(expectedPerson));
expect(value).toBeNull() Checks that the value is null expect(result, isNull);
expect(value).toBeTrue() Checks that the value is true expect(isActive, isTrue);
expect(value).toBeFalse() Checks that the value is false expect(isActive, isFalse);
expect(value).toBeLessThan(expected) Checks that the value is less than the expected value expect(score, lessThan(100));
expect(value).toBeGreaterThan(expected) Checks that the value is greater than the expected value expect(age, greaterThan(18));
expect(value).toBeCloseTo(expected, precision) Checks that the value is close to the expected value within a given precision expect(pi, closeTo(3.14, 0.01));
expect(value).toContain(expected) Checks that the value contains the expected element (for lists or strings) expect(names, contains('John'));
expect(value).toHaveMethod(methodName) Checks that the value has a method with the given name expect(person, hasMethod('getName'));
expect(value).toHaveProperty(propertyName) Checks that the value has a property with the given name expect(person, hasProperty('name'));
Note: The expect() function is part of the matcher package in Flutter tests. It provides a fluent API for making assertions about values.
Warning: When using toBe(), be cautious with floating-point numbers, as direct equality checks might fail due to precision issues. Use toBeCloseTo() instead for such cases.

5. Using setUp() and tearDown() Hooks

The setUp() and tearDown() hooks are essential for organizing test setup and cleanup in Flutter unit tests. These hooks allow you to define code that runs before and after each test case, ensuring consistent test environments.

Hook Description Usage
setUp() Runs before each test case in the test group Used to initialize variables, mocks, or dependencies needed for tests
tearDown() Runs after each test case in the test group Used to clean up resources or reset state after each test

Example: Using setUp() and tearDown()

import 'package:flutter_test/flutter_test.dart';

void main() {
  group('User Repository Tests', () {
    late UserRepository repo;
    late MockDatabase database;

    setUp(() {
      database = MockDatabase();
      repo = UserRepository(database);
    });

    tearDown(() {
      // Clean up any resources if needed
      database.clear();
    });

    test('should fetch user from database', () {
      when(database.fetchUser(1)).thenReturn(User(id: 1, name: 'John'));
      final user = repo.fetchUser(1);
      expect(user.name, 'John');
    });
  });
}
Note: setUp() and tearDown() are scoped to the nearest group(). Each test in a group will have its own instance of setup and teardown logic.

6. Using group() for Test Organization

Example: Organizing Tests with group()

import 'package:flutter_test/flutter_test.dart';

void main() {
  group('User Authentication', () {
    group('Login Flow', () {
      test('should login successfully with valid credentials', () async {
        // Test implementation
      });

      test('should show error for invalid password', () async {
        // Test implementation
      });
    });

    group('Logout Flow', () {
      test('should logout user and clear session', () async {
        // Test implementation
      });
    });
  });
}
Method Description Usage
group() Groups related tests together for better organization group('Test Suite Name', () { ... });
Note: The group() function helps in organizing tests into logical blocks, making test suites more readable and maintainable.

7. Testing Async Code

Method Description Example
testWidgets Used for testing widget trees that involve async operations.
testWidgets('fetches data asynchronously', (WidgetTester tester) async {
  await tester.pumpWidget(MyApp());
  expect(find.text('Loading...'), findsOneWidget);
  await tester.pumpAndSettle();
  expect(find.text('Data loaded'), findsOneWidget);
});
pumpAndSettle Waits for all async operations to complete before proceeding. await tester.pumpAndSettle(Duration(seconds: 5));
pump Rebuilds the widget tree and waits for a frame to be rendered. await tester.pump(const Duration(milliseconds: 500));

Example: Testing Async API Call

import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;

void main() {
  group('Async API Test', () {
    test('fetches data from API', () async {
      final response = await http.get(Uri.parse('https://api.example.com/data'));
      expect(response.statusCode, 200);
    });
  });
}
Note: When testing async code, always use await to ensure the test waits for asynchronous operations to complete.
Warning: Avoid using Future.delayed in tests unless necessary, as it can make tests flaky.

8. Mocking Dependencies

Example: Mocking HTTP Client

import 'package:flutter_test/flutter_test.dart';
import 'package:http/http.dart' as http;

// Mock implementation
class MockHttpClient implements http.Client {
  @override
  Future<http.Response> get(Uri url) async {
    if (url.path == '/users') {
      return http.Response('{"name": "John"}', 200);
    }
    return http.Response('', 404);
  }
}

void main() {
  test('Fetch user data', () async {
    final client = MockHttpClient();
    final response = await client.get(Uri.parse('http://example.com/users'));
    expect(response.statusCode, 200);
  });
}
Feature Description Usage
mockito Popular mocking library for Dart Mockito provides easy-to-use mocking capabilities
mocktail Modern, lightweight mocking library Alternative to Mockito with simpler API
Note: When mocking dependencies, ensure that the mock returns consistent data to avoid flaky tests.

Mockito vs Mocktail

  • Mockito: More mature, feature-rich, but verbose
  • Mocktail: Simpler syntax, less boilerplate

9. Using @GenerateMocks Annotation

The @GenerateMocks annotation in Flutter is used to automatically generate mock classes for your test files. It simplifies the process of creating mocks for dependencies, reducing boilerplate code and making tests more maintainable.

Annotation Description Usage
@GenerateMocks Generates mock classes for specified dependencies. @GenerateMocks([MyService, UserRepository])

Example: Using @GenerateMocks

@GenerateMocks([MyService, UserRepository])
void main() {
  final mocks = <Mock>[];
  // Mocks are generated and can be used in tests
}
Note: The @GenerateMocks annotation requires the mockito package to be included in your project dependencies.
Warning: Ensure that the build_runner package is added to your project and run flutter pub run build_runner build after adding the annotation to generate the mock classes.

10. Running Tests

Example: Running All Tests

flutter test

Example: Running Tests in a Specific File

flutter test test/my_widget_test.dart

Example: Running Tests with Coverage

flutter test --coverage

Example: Running Tests in Headless Mode

flutter test --machine
Flag Description Usage
--coverage Generates code coverage report flutter test --coverage
--machine Runs tests in machine-readable format flutter test --machine
--verbose Shows detailed output during test run flutter test --verbose
--timeout Sets timeout for test execution flutter test --timeout 30s

Was this article helpful?