티스토리 뷰
Flutter 개발에서 단위 테스트는 앱의 기능을 검증하는 필수 과정입니다. 하지만 때때로 특정 함수나 외부 의존성을 테스트할 수 없을 때 Mocking 기법이 필요합니다. 이번 포스트에서는 Flutter에서 Mocking을 활용하는 방법을 이해하기 쉽게 설명하고, 테스트 코드 작성 시 유용한 팁과 사례를 공유하겠습니다.
참고. Mock dependencies using Mockito
Mocking이란 무엇인가?
Mocking은 단위 테스트를 실행할 때 외부 의존성이나 복잡한 객체를 가짜(Mock) 객체로 대체하는 기법입니다. 예를 들어 네트워크 요청, 데이터베이스 호출, API 응답 등 외부 요소를 테스트할 수 없는 상황에서 Mock 객체를 사용하여 코드의 동작을 검증할 수 있습니다.
Mocking의 장점
- 의존성 제거: 네트워크, 데이터베이스 등 외부 환경에 의존하지 않습니다.
- 테스트 속도 향상: 가벼운 Mock 객체를 사용하므로 테스트가 더 빠르게 실행됩니다.
- 유연한 검증: 특정 함수의 반환값을 설정하여 다양한 시나리오를 테스트할 수 있습니다.
Flutter에서 Mocking 설정하기
Flutter에서는 Mocking을 위해 주로 mockito
패키지를 사용합니다. mockito
는 간단하고 강력한 Mock 객체 생성을 지원합니다.
1. mockito
패키지 설치
먼저 pubspec.yaml
파일에 mockito
패키지를 추가합니다:
dev_dependencies:
mockito: ^5.0.0
flutter_test:
sdk: flutter
이후 패키지를 설치합니다:
flutter pub get
2. 테스트 파일 생성
테스트 파일은 test
디렉토리에 저장되며, 예시 이름은 api_service_test.dart
입니다.
Mock 객체 생성 및 사용하기
1. Mock 클래스 생성
테스트할 클래스를 Mock 객체로 만들기 위해 mockito
의 Mock
클래스를 확장합니다.
예를 들어 ApiService
라는 클래스가 있다고 가정해 보겠습니다:
class ApiService {
Future<String> fetchData() async {
// 실제 네트워크 요청 로직
return "Real Data";
}
}
이 클래스를 테스트하기 위해 Mock 클래스를 생성합니다:
import 'package:mockito/mockito.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:my_app/api_service.dart';
// Mock 클래스 생성
class MockApiService extends Mock implements ApiService {}
2. Mock 객체 사용
Mock 객체를 사용하여 테스트를 작성하는 예제는 다음과 같습니다:
void main() {
group('ApiService 테스트', () {
// Mock 객체 선언
late MockApiService mockApiService;
setUp(() {
mockApiService = MockApiService();
});
test('fetchData가 "Mock Data"를 반환하는지 확인', () async {
// Mock 객체의 반환값 설정
when(mockApiService.fetchData()).thenAnswer((_) async => "Mock Data");
// 테스트 실행
final result = await mockApiService.fetchData();
// 결과 검증
expect(result, "Mock Data");
});
});
}
주요 함수와 개념
1. when
과 thenAnswer
when
: 특정 함수가 호출될 때 동작을 정의합니다.thenAnswer
: 비동기 함수의 반환값을 설정할 때 사용합니다.- 예:
when(mockApiService.fetchData()).thenAnswer((_) async => "Mock Result");
2. verify
- 특정 함수가 호출되었는지 확인할 수 있습니다.
- 예:
verify(mockApiService.fetchData()).called(1);
3. any
와 captureAny
any
: 인자 값에 관계없이 호출을 검증합니다.captureAny
: 인자 값을 캡처합니다.- 예:
when(mockApiService.fetchData(any)).thenReturn("Mock Value");
모범 사례 및 팁
- 단일 동작 검증: 각 테스트 케이스는 하나의 동작만 검증하도록 작성하세요.
- 실제 클래스와 Mock 객체 분리: Mock 객체는 테스트 코드에서만 사용해야 합니다.
- 가짜 반환값 다양화: 다양한 시나리오(성공, 실패 등)를 가정하여 여러 반환값을 설정해보세요.
setUp
과tearDown
사용: 테스트 전후로 초기화 작업을 수행합니다.
Flutter Mocking의 활용 사례
- 네트워크 API 테스트: 외부 API 호출 대신 Mock 데이터를 반환합니다.
- 데이터베이스 검증: 데이터베이스 호출을 가짜 객체로 대체합니다.
- UI 로직 테스트: 위젯 테스트에서 비즈니스 로직을 검증할 때 활용합니다.
결론
Mocking은 Flutter 단위 테스트에서 필수적인 기술입니다. mockito
패키지를 활용하면 복잡한 외부 의존성을 제거하고 더 빠르고 유연한 테스트를 작성할 수 있습니다. 앱의 품질을 높이기 위해 Mock 객체를 적극적으로 활용해 보세요.
Flutter 개발에서 단위 테스트와 Mocking을 완벽하게 마스터한다면 안정적이고 확장 가능한 앱을 개발할 수 있을 것입니다.
import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
Future<Album> fetchAlbum(http.Client client) async {
final response = await client
.get(Uri.parse('https://jsonplaceholder.typicode.com/albums/1'));
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
return Album.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
} else {
// If the server did not return a 200 OK response,
// then throw an exception.
throw Exception('Failed to load album');
}
}
class Album {
final int userId;
final int id;
final String title;
const Album({required this.userId, required this.id, required this.title});
factory Album.fromJson(Map<String, dynamic> json) {
return Album(
userId: json['userId'] as int,
id: json['id'] as int,
title: json['title'] as String,
);
}
}
void main() => runApp(const MyApp());
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late final Future<Album> futureAlbum;
@override
void initState() {
super.initState();
futureAlbum = fetchAlbum(http.Client());
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Fetch Data Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
),
home: Scaffold(
appBar: AppBar(
title: const Text('Fetch Data Example'),
),
body: Center(
child: FutureBuilder<Album>(
future: futureAlbum,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data!.title);
} else if (snapshot.hasError) {
return Text('${snapshot.error}');
}
// By default, show a loading spinner.
return const CircularProgressIndicator();
},
),
),
),
);
}
}
'Flutter Cookbook' 카테고리의 다른 글
Flutter 위젯 테스트: Finder 사용법 완벽 가이드 (1) | 2024.12.19 |
---|---|
Flutter 위젯 테스트 완벽 가이드: 안정적인 UI를 위한 첫걸음 (0) | 2024.12.18 |
Flutter 단위 테스트(Unit Testing) 완벽 가이드: 고품질 앱을 만드는 핵심 (0) | 2024.12.16 |
Flutter 통합 테스트에서 성능 프로파일링: 최적화된 앱 개발의 비결 (1) | 2024.12.15 |
Flutter 통합 테스트 완벽 가이드: 안정적인 앱 개발을 위한 첫걸음 (2) | 2024.12.14 |