Fake vs mock: mocktail ilə praktika
Rəsmi tövsiyələr "test üçün fake-lər yazın" bəndini ən yüksək prioritetdə verir və səbəbi konkretdir: fake obyektin daxili işini deyil, giriş və çıxışını yoxlayır — bu isə modul, yaxşı təyin olunmuş interfeyslər yazmağa məcbur edir.
Fərqi qısa formalaşdırmaq olar:
- Fake — müqavilənin sadələşdirilmiş, lakin işlək implementasiyası.
addçağırıldıqda element həqiqətən əlavə olunur; siyahı yaddaşda saxlanılır. Bir dəfə yazılır, onlarla testdə işlədilir. - Mock — davranışı olmayan, yalnız cavab verməyə proqramlaşdırılmış obyekt. Hər testdə
when(() => ...)quraşdırması yazılır vəverify(...)ilə çağırışlar yoxlanılır.
Seçim meyarı bir sualdır: "nəyi yoxlayıram — nəticəni, yoxsa çağırışı?"
- Nəticə (səbətdə 3 element var, yekun 32 manatdır, status
cancelledoldu) → fake. - Çağırış (analitika hadisəsi göndərildi, cache invalidate olundu,
logoutbir dəfə çağırıldı) → mock.
Dart-da mock üçün əsas alət mocktail-dır: kod generasiyası tələb etmir, MockCat extends Mock implements Cat kimi elan olunur və çağırışlar closure ilə yazılır — when(() => cat.sound()).thenReturn('meow').
import 'package:mocktail/mocktail.dart';
import 'package:flutter_test/flutter_test.dart';
// 1) Elan: kod generasiyası YOXDUR, `build_runner` lazım deyil.
class MockAnalyticsService extends Mock implements AnalyticsService {}
class MockOrderRepository extends Mock implements OrderRepository {}
// 2) Öz tiplərin `any()` ilə işlədilirsə, fallback qeyd olunmalıdır.
class _FakeOrder extends Fake implements Order {}
void main() {
setUpAll(() {
// `any()` matcher-ı işlətməzdən əvvəl bir dəfə çağırılır.
registerFallbackValue(_FakeOrder());
});
late MockAnalyticsService analytics;
setUp(() {
analytics = MockAnalyticsService();
// Void metodlar üçün: thenAnswer((_) async {})
when(() => analytics.track(any(), properties: any(named: 'properties')))
.thenAnswer((_) async {});
});
test('sifariş verildikdə analitika hadisəsi göndərilir', () async {
final notifier = CheckoutNotifier(
repository: FakeOrderRepository([]), // NƏTİCƏ üçün fake
analytics: analytics, // ÇAĞIRIŞ üçün mock
);
await notifier.submit(testForm);
// Çağırışın yoxlanılması — burada mock haqlıdır,
// çünki "nəticə" yoxdur: hadisə göndərildi, vəssalam.
verify(() => analytics.track('order_placed',
properties: any(named: 'properties'))).called(1);
});
test('xəta halında analitika hadisəsi göndərilmir', () async {
final notifier = CheckoutNotifier(
repository: FakeOrderRepository([], placeError: const NetworkFailure()),
analytics: analytics,
);
await notifier.submit(testForm);
verifyNever(() => analytics.track('order_placed',
properties: any(named: 'properties')));
});
test('stub-ın qaytardığı dəyər', () {
final repo = MockOrderRepository();
// Sinxron dəyər üçün thenReturn, Future üçün thenAnswer.
when(() => repo.fetchMine())
.thenAnswer((_) async => Result.ok(const <Order>[]));
// Arqumentə görə fərqli cavab:
when(() => repo.cancel('o-1'))
.thenAnswer((_) async => Result.ok(cancelledOrder));
when(() => repo.cancel('o-2'))
.thenAnswer((_) async => const Result.error(NotFoundFailure()));
// Xəta atmaq:
when(() => repo.fetchById(any())).thenThrow(const NetworkFailure());
});
}
// mocktail-in mockito-dan əsas fərqləri:
// • kod generasiyası yoxdur (@GenerateMocks, build_runner lazım deyil)
// • çağırışlar closure-a bükülür: when(() => ...), verify(() => ...)
// • tip-spesifik matcher-lar (anyString, anyInt) yerinə vahid `any()`,
// `any(named: '...')`, `any(that: ...)`mocktail-ın əsas API-si: elan, stub, yoxlama və `registerFallbackValue`.
| Meyar | Fake | Mock (mocktail) |
|---|---|---|
| Nə yoxlanılır | Nəticə (state, qaytarılan dəyər) | Çağırışın baş verməsi |
| Yazılma qiyməti | Bir dəfə, 20-40 sətir | Bir sətir elan, lakin hər testdə `when` quraşdırması |
| Təkrar istifadə | Yüksək — bütün testlərdə | Aşağı — quraşdırma testə xasdır |
| Refaktora həssaslıq | Aşağı — müqavilə dəyişməsə işləyir | Yüksək — metod adı/parametr dəyişdikdə sınır |
| Real davranış | Var (yaddaşda state saxlayır) | Yoxdur (yalnız cavab verir) |
| Ən uyğun yer | Repository, service, use-case müqavilələri | Analitika, loqlama, bildiriş — yan effektlər |
Yaxşı fake-in üç xüsusiyyəti.
1. İşlək state. Yaddaşda siyahı ya da Map saxlayır; add, update, delete real təsir edir. Bu, testin "iki dəfə əlavə etdikdə say artır" kimi davranışları yoxlamasına imkan verir.
2. Xəta inyeksiyası. Konstruktor parametri ilə xəta yolunu açmaq: FakeOrderRepository(const [], fetchError: NetworkFailure()). Belə olduqda hər xəta halı üçün ayrı fake sinfi lazım gəlmir.
3. Sayğaclar. fetchCount, cancelCount — cache və birləşdirmə davranışını yoxlamaq üçün. Bu, mock-un verify-ına oxşar imkan verir, lakin fake-in davranışını itirmədən.
Nə vaxt gecikmə əlavə etmək. Test fake-ində gecikmə olmamalıdır — test sürətli olsun. Gecikmə yalnız iki halda lazımdır: (1) ...Local implementasiyasında (əvvəlki mərhələ — loading state-ini gözlə görmək üçün), (2) konkret bir testdə yarış vəziyyətini yoxlamaq üçün (placeDelay parametri ilə).
Fixture-lar (test məlumatı). Hər testdə Order(id: 'o-1', title: ..., status: ..., total: ...) yazmaq təkrardır və dəyişikliyə həssasdır: modelə yeni məcburi sahə əlavə etdikdə 40 test faylı sınır. Həlli — test/fixtures/ qovluğunda builder funksiyaları: anOrder(status: OrderStatus.pending). Yeni sahə əlavə olunduqda yalnız builder dəyişir.
mocktail-ın tələsi. any() matcher-ı öz tiplərinlə işlədilirsə, registerFallbackValue çağırılmalıdır — əks halda test icra vaxtı xəta verir. Bunu setUpAll-da bir dəfə etmək kifayətdir.
Praktika. İki addım:
1. test/fixtures/order_fixtures.dart faylı yarat: Order anOrder({String? id, OrderStatus? status, double? total}) builder-i — bütün sahələrin defolt dəyəri olsun.
2. Bir repository müqaviləsi üçün tam fake yaz: işlək state + xəta inyeksiyası (konstruktor parametri) + iki sayğac. Sonra onu ən azı üç testdə işlət: notifier testi, widget testi və xəta yolu testi.
Hazır sayılır: modelə yeni sahə əlavə etdikdə yalnız builder faylı dəyişir (testlər sınmır), və xəta halını yoxlamaq üçün yeni sinif yazmaq lazım gəlmir.
📚 Mənbələr və sənədlər
- mocktail paketirəsmipub.dev
`when(() => ...)`, `verify(() => ...)`, `any()`, `registerFallbackValue` — bu mövzudaki API-nin mənbəyi.
- Arxitektura tövsiyələri: fake-lərrəsmidocs.flutter.dev
"Test üçün fake-lər yazın" tövsiyəsi və onun əsaslandırması.
- Case study: hər qatın testirəsmidocs.flutter.dev
Real layihədə `FakeBookingRepository`, `FakeApiClient` kimi fake-lərin necə yazıldığı.
- Flutter: unit testlərrəsmidocs.flutter.dev
`setUp`, `setUpAll`, `group` və test strukturunun əsasları.