Flutter Riverpod 테스트 전략 — Mocking Providers, Integration Tests, 그리고 CI
요약
본 문서는 Flutter Riverpod 기반 애플리케이션의 효과적인 테스트 전략을 제시합니다. 단위 테스트 시에는 `ProviderContainer`와 `overrides`를 사용하여 실제 리포지토리 구현체 대신 가짜(Fake) 객체를 주입하여 격리된 테스트가 가능하며, 위젯 테스트에서는 `ProviderScope`를 활용해 오버라이딩합니다. 또한, CI/CD 환경에서는 `flutter test --coverage`를 실행하고 Codecov와 같은 도구를 사용하여 커버리지를 관리하는 방법을 안내합니다.
핵심 포인트
- Riverpod의 `overrides` 메커니즘을 활용하여 Mockito 없이도 효과적인 단위 테스트가 가능합니다.
- 단위 로직 테스트는 `ProviderContainer`를, UI 위젯 테스트는 `ProviderScope`를 사용하여 Provider 의존성을 오버라이딩해야 합니다.
- 테스트 시 실제 구현체 대신 Fake 객체를 사용함으로써 테스트의 독립성과 예측 가능성을 높일 수 있습니다.
- CI 환경에서는 커버리지 측정을 위해 `flutter test --coverage` 명령어를 실행하고 결과를 업로드하는 것이 중요합니다.
Flutter Riverpod 테스트 전략 — Mocking Providers, Integration Tests, 그리고 CI
Riverpod 기반 앱을 올바르게 테스트하는 방법.
ProviderContainer 를 사용한 단위 테스트
// 테스트 대상: 사용자를 가져오는 Provider
final usersProvider = FutureProvider<List<User>>((ref) async {
final repo = ref.watch(userRepositoryProvider);
return repo.getUsers();
});
// 테스트
void main() {
test('usersProvider returns a list of users', () async {
final container = ProviderContainer(
overrides: [
userRepositoryProvider.overrideWithValue(FakeUserRepository()),
],
);
addTearDown(container.dispose);
final users = await container.read(usersProvider.future);
expect(users, hasLength(3));
});
}
class FakeUserRepository implements UserRepository {
@override
Future<List<User>> getUsers() async => [
User(id: '1', name: 'Alice'),
User(id: '2', name: 'Bob'),
User(id: '3', name: 'Charlie'),
];
}
Widget 테스트: ProviderScope 로 Override
testWidgets('UserListPage displays users', (tester) async {
await tester.pumpWidget(
ProviderScope(
overrides: [
userRepositoryProvider.overrideWithValue(FakeUserRepository()),
],
child: const MaterialApp(home: UserListPage()),
),
);
// FutureProvider 가 해결될 때까지 대기
await tester.pumpAndSettle();
expect(find.text('Alice'), findsOneWidget);
expect(find.text('Bob'), findsOneWidget);
});
AsyncNotifier 테스트
final counterProvider = AsyncNotifierProvider<CounterNotifier, int>(CounterNotifier.new,);
class CounterNotifier extends AsyncNotifier<int> {
@override
Future<int> build() async => 0;
Future<void> increment() async {
state = const AsyncLoading();
state = AsyncData((state.value ?? 0) + 1);
}
}
// 테스트
test('CounterNotifier increments', () async {
final container = ProviderContainer();
addTearDown(container.dispose);
await container.read(counterProvider.future);
// build() 완료 대기
await container.read(counterProvider.notifier).increment();
expect(container.read(counterProvider).value, equals(1));
});
CI 통합: GitHub Actions
#.github/workflows/flutter-test.yml
- name: Flutter Test
run: flutter test --coverage - name: Upload coverage
uses: codecov/codecov-action@v3
with:
file: coverage/lcov.info
요약
단위 테스트 → ProviderContainer + override
Widget 테스트 → ProviderScope + override
CI → flutter test --coverage + Codecov
핵심 규칙 → 실제 구현체를 FakeXxx 로 대체 (Mockito 불필요)
Riverpod 의 override 메커니즘을 사용하면 Mockito 없이 자체적으로 독립적인 테스트를 작성할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기