Что вы использовали для написания unit-тестов во Flutter?

«Что вы использовали для написания unit-тестов во Flutter?» — вопрос из категории Тестирование, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В моих Flutter-проектах для unit-тестирования я использую следующий стек:

Основные пакеты:

  • test / flutter_test: основной фреймворк для написания тестов и ассертов.
  • mocktail: для создания моков зависимостей. Предпочитаю его из-за null-safety и отсутствия необходимости в кодогенерации.
  • bloc_test: если в проекте используется BLoC, этот пакет значительно упрощает тестирование cubit'ов и bloc'ов.

Пример теста бизнес-логики (без UI):

import 'package:test/test.dart';

class Counter {
  int value = 0;
  void increment() => value++;
  void decrement() => value--;
}

void main() {
  group('Counter', () {
    late Counter counter;

    setUp(() {
      // Инициализация перед каждым тестом
      counter = Counter();
    });

    test('value should start at 0', () {
      expect(counter.value, 0);
    });

    test('value should be 1 after increment', () {
      counter.increment();
      expect(counter.value, 1);
    });

    test('value should be -1 after decrement', () {
      counter.decrement();
      expect(counter.value, -1);
    });
  });
}

Пример теста с моком (mocktail):

import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

// Репозиторий (зависимость)
abstract class UserRepository {
  Future<String> fetchUserName(int id);
}

// Сервис, который тестируем
class UserService {
  final UserRepository repository;
  UserService(this.repository);

  Future<String> getUserName(int id) async {
    return 'User: ${await repository.fetchUserName(id)}';
  }
}

// Мок репозитория
class MockUserRepository extends Mock implements UserRepository {}

void main() {
  late UserService userService;
  late MockUserRepository mockRepository;

  setUp(() {
    mockRepository = MockUserRepository();
    userService = UserService(mockRepository);
  });

  test('getUserName should format data from repository', () async {
    // Настраиваем мок
    when(() => mockRepository.fetchUserName(1))
        .thenAnswer((_) async => 'John');

    // Выполняем и проверяем
    final result = await userService.getUserName(1);
    expect(result, 'User: John');
    // Проверяем, что метод был вызван с правильным аргументом
    verify(() => mockRepository.fetchUserName(1)).called(1);
  });
}

Ключевые принципы, которых я придерживаюсь:

  1. Изоляция: Каждый тест должен быть независимым и не полагаться на состояние, созданное другими тестами.
  2. Одна ответственность: Тест проверяет одну конкретную функциональность или сценарий.
  3. Читаемость: Использую setUp/tearDown для подготовки и очистки, а group для логической группировки тестов.
  4. Скорость: Unit-тесты должны выполняться быстро, без эмулятора/симулятора.