Testing
Giriş
Nylo Website v7, Laravel'in test araçlarından ilham alan kapsamlı bir test framework'ü içerir. Sağladıkları:
- Otomatik kurulum/temizleme ile test fonksiyonları (
nyTest,nyWidgetTest,nyGroup) NyTest.actingAs<T>()ile kimlik doğrulama simülasyonu- Testlerde zamanı dondurma veya manipüle etme ile zaman yolculuğu
- URL kalıp eşleştirme ve çağrı takibi ile API mocklama
- Yerleşik sahte veri üreteci (
NyFaker) ile factory'ler - Güvenli depolama, path provider ve daha fazlası için platform kanalı mocklama
- Rotalar, Backpack, kimlik doğrulama ve ortam için özel doğrulamalar
Başlarken
Test dosyanızın başında test framework'ünü başlatın:
import 'package:nylo_framework/nylo_framework.dart';
void main() {
NyTest.init();
nyTest('my first test', () async {
expect(1 + 1, equals(2));
});
}
NyTest.init() test ortamını kurar ve autoReset: true olduğunda (varsayılan) testler arasında otomatik durum sıfırlamayı etkinleştirir.
Test Yazma
nyTest
Test yazmak için birincil fonksiyon:
nyTest('can save and read from storage', () async {
backpackSave("name", "Anthony");
expect(backpackRead<String>("name"), equals("Anthony"));
});
Seçenekler:
nyTest('my test', () async {
// test body
}, skip: false, timeout: Timeout(Duration(seconds: 30)));
nyWidgetTest
WidgetTester ile Flutter widget'larını test etmek için:
nyWidgetTest('renders a button', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(
home: Scaffold(
body: ElevatedButton(
onPressed: () {},
child: Text("Tap me"),
),
),
));
expect(find.text("Tap me"), findsOneWidget);
});
Widget Test Araçları
NyWidgetTest sınıfı ve WidgetTester uzantıları, Nylo widget'larını doğru tema desteğiyle pompalamak, init() tamamlanmasını beklemek ve yükleme durumlarını test etmek için yardımcılar sağlar.
Test Ortamını Yapılandırma
Google Fonts almayı devre dışı bırakmak ve isteğe bağlı olarak özel bir tema ayarlamak için setUpAll içinde NyWidgetTest.configure() çağırın:
nySetUpAll(() async {
NyWidgetTest.configure(testTheme: ThemeData.light());
await setupApplication(providers);
});
Yapılandırmayı NyWidgetTest.reset() ile sıfırlayabilirsiniz.
Yazı tipi gerektirmeyen test için iki yerleşik tema mevcuttur:
ThemeData light = NyWidgetTest.simpleTestTheme;
ThemeData dark = NyWidgetTest.simpleDarkTestTheme;
Nylo Widget'larını Pompalama
Bir widget'ı tema desteği ile MaterialApp içine sarmak için pumpNyWidget kullanın:
nyWidgetTest('renders page', (tester) async {
await tester.pumpNyWidget(
HomePage(),
theme: ThemeData.light(),
darkTheme: ThemeData.dark(),
themeMode: ThemeMode.light,
settleTimeout: Duration(seconds: 5),
useSimpleTheme: false,
);
expect(find.text('Welcome'), findsOneWidget);
});
Yazı tipi gerektirmeyen tema ile hızlı pompalama için:
await tester.pumpNyWidgetSimple(HomePage());
Init Bekleme
pumpNyWidgetAndWaitForInit, yükleme göstergeleri kaybolana (veya zaman aşımına ulaşılana) kadar frame pompalar; bu, asenkron init() metotlarına sahip sayfalar için kullanışlıdır:
await tester.pumpNyWidgetAndWaitForInit(
HomePage(),
timeout: Duration(seconds: 10),
useSimpleTheme: true,
);
// init() has completed
expect(find.text('Loaded Data'), findsOneWidget);
Pompa Yardımcıları
// Pump frames until a specific widget appears
bool found = await tester.pumpUntilFound(
find.text('Welcome'),
timeout: Duration(seconds: 5),
);
// Settle gracefully (won't throw on timeout)
await tester.pumpAndSettleGracefully(timeout: Duration(seconds: 5));
Yaşam Döngüsü Simülasyonu
Widget ağacındaki herhangi bir NyPage üzerinde AppLifecycleState değişikliklerini simüle edin:
await tester.pumpNyWidget(MyPage());
await tester.simulateLifecycleState(AppLifecycleState.paused);
await tester.pump();
// Assert side effects of the paused lifecycle action
Yükleme ve Kilit Kontrolleri
NyPage/NyState widget'larında adlandırılmış yükleme anahtarlarını ve kilitleri kontrol edin:
// Check if a named loading key is active
bool loading = tester.isLoadingNamed(find.byType(MyPage), name: 'fetchUsers');
// Check if a named lock is held
bool locked = tester.isLockedNamed(find.byType(MyPage), name: 'submit');
// Check for any loading indicator (CircularProgressIndicator or Skeletonizer)
bool isAnyLoading = tester.isLoading();
testNyPage Yardımcısı
Bir NyPage'i pompalar, init'i bekler, ardından beklentilerinizi çalıştıran pratik bir fonksiyon:
testNyPage(
'HomePage loads correctly',
build: () => HomePage(),
expectations: (tester) async {
expect(find.text('Welcome'), findsOneWidget);
},
useSimpleTheme: true,
initTimeout: Duration(seconds: 10),
skip: false,
);
testNyPageLoading Yardımcısı
Bir sayfanın init() sırasında yükleme göstergesi gösterdiğini test edin:
testNyPageLoading(
'HomePage shows loading state',
build: () => HomePage(),
skip: false,
);
NyPageTestMixin
Yaygın sayfa test araçları sağlayan bir mixin:
class HomePageTest with NyPageTestMixin {
void runTests(WidgetTester tester) async {
// Verify init was called and loading completed
await verifyInitCalled(tester, HomePage(), timeout: Duration(seconds: 5));
// Verify loading state is shown during init
await verifyLoadingState(tester, HomePage());
}
}
nyGroup
İlgili testleri bir arada gruplandırın:
nyGroup('Authentication', () {
nyTest('can login', () async {
NyTest.actingAs<User>(User(name: "Anthony"));
expectAuthenticated<User>();
});
nyTest('can logout', () async {
NyTest.actingAs<User>(User(name: "Anthony"));
NyTest.logout();
expectGuest();
});
});
Test Yaşam Döngüsü
Yaşam döngüsü kancalarını kullanarak kurulum ve temizleme mantığı ayarlayın:
void main() {
NyTest.init();
nySetUpAll(() {
// Runs once before all tests
});
nySetUp(() {
// Runs before each test
});
nyTearDown(() {
// Runs after each test
});
nyTearDownAll(() {
// Runs once after all tests
});
}
Testleri Atlama ve CI Testleri
// Skip a test with a reason
nySkip('not implemented yet', () async {
// ...
}, "Waiting for API update");
// Tests expected to fail
nyFailing('known bug', () async {
// ...
});
// CI-only tests (tagged with 'ci')
nyCi('integration test', () async {
// Only runs in CI environments
});
Kimlik Doğrulama
Testlerde kimliği doğrulanmış kullanıcıları simüle edin:
nyTest('user can access profile', () async {
// Simulate a logged-in user
NyTest.actingAs<User>(User(name: "Anthony", email: "anthony@example.com"));
// Verify authenticated
expectAuthenticated<User>();
// Access the acting user
User? user = NyTest.actingUser<User>();
expect(user?.name, equals("Anthony"));
});
nyTest('guest cannot access profile', () async {
// Verify not authenticated
expectGuest();
});
Kullanıcının oturumunu kapatın:
NyTest.logout();
expectGuest();
Zaman Yolculuğu
NyTime kullanarak testlerinizde zamanı manipüle edin:
Belirli Bir Tarihe Atlama
nyTest('time travel to 2025', () async {
NyTest.travel(DateTime(2025, 1, 1));
expect(NyTime.now().year, equals(2025));
NyTest.travelBack(); // Reset to real time
});
Zamanı İleri veya Geri Alma
NyTest.travelForward(Duration(days: 30)); // Jump 30 days ahead
NyTest.travelBackward(Duration(hours: 2)); // Go back 2 hours
Zamanı Dondurma
NyTest.freezeTime(); // Freeze at the current moment
DateTime frozen = NyTime.now();
await Future.delayed(Duration(seconds: 1));
expect(NyTime.now(), equals(frozen)); // Time hasn't moved
NyTest.travelBack(); // Unfreeze
Zaman Sınırları
NyTime.travelToStartOfDay(); // 00:00:00.000
NyTime.travelToEndOfDay(); // 23:59:59.999
NyTime.travelToStartOfMonth(); // 1st of current month
NyTime.travelToEndOfMonth(); // Last day of current month
NyTime.travelToStartOfYear(); // Jan 1st
NyTime.travelToEndOfYear(); // Dec 31st
Kapsamlı Zaman Yolculuğu
Dondurulmuş zaman bağlamında kod yürütün:
await NyTime.withFrozenTime<void>(DateTime(2025, 6, 15), () async {
expect(NyTime.now(), equals(DateTime(2025, 6, 15)));
});
// Time is automatically restored after the callback
API Mocklama
URL Kalıbına Göre Mocklama
Joker karakter desteğiyle URL kalıplarını kullanarak API yanıtlarını mocklayın:
nyTest('mock API responses', () async {
// Exact URL match
NyMockApi.respond('/users/1', {'id': 1, 'name': 'Anthony'});
// Single segment wildcard (*)
NyMockApi.respond('/users/*', {'id': 1, 'name': 'User'});
// Multi-segment wildcard (**)
NyMockApi.respond('/api/**', {'status': 'ok'});
// With status code and headers
NyMockApi.respond(
'/users',
{'error': 'Unauthorized'},
statusCode: 401,
method: 'POST',
headers: {'X-Error': 'true'},
);
// With simulated delay
NyMockApi.respond(
'/slow-endpoint',
{'data': 'loaded'},
delay: Duration(seconds: 2),
);
});
API Servis Türüne Göre Mocklama
Tüm bir API servisini türüne göre mocklayın:
nyTest('mock API service', () async {
NyMockApi.register<UserApiService>((MockApiRequest request) async {
if (request.endpoint.contains('/users')) {
return {'users': [{'id': 1, 'name': 'Anthony'}]};
}
return {'error': 'not found'};
});
});
Çağrı Geçmişi ve Doğrulamalar
API çağrılarını takip edin ve doğrulayın:
nyTest('verify API was called', () async {
NyMockApi.setRecordCalls(true);
// ... perform actions that trigger API calls ...
// Assert endpoint was called
expectApiCalled('/users');
// Assert endpoint was not called
expectApiNotCalled('/admin');
// Assert call count
expectApiCalled('/users', times: 2);
// Assert specific method
expectApiCalled('/users', method: 'POST');
// Get call details
List<ApiCallInfo> calls = NyMockApi.getCallsFor('/users');
});
Mock Yanıtları Oluşturma
Response<Map<String, dynamic>> response = NyMockApi.createResponse(
data: {'id': 1, 'name': 'Anthony'},
statusCode: 200,
statusMessage: 'OK',
);
Factory'ler
Factory Tanımlama
Modellerinizin test örneklerinin nasıl oluşturulacağını tanımlayın:
NyFactory.define<User>((NyFaker faker) => User(
name: faker.name(),
email: faker.email(),
phone: faker.phone(),
));
Geçersiz kılma desteği ile:
NyFactory.defineWithOverrides<User>((NyFaker faker, Map<String, dynamic> attributes) => User(
name: attributes['name'] ?? faker.name(),
email: attributes['email'] ?? faker.email(),
phone: attributes['phone'] ?? faker.phone(),
));
Factory Durumları
Bir factory'nin varyasyonlarını tanımlayın:
NyFactory.state<User>('admin', (User user, NyFaker faker) {
return User(name: user.name, email: user.email, role: 'admin');
});
NyFactory.state<User>('premium', (User user, NyFaker faker) {
return User(name: user.name, email: user.email, subscription: 'premium');
});
Örnek Oluşturma
// Create a single instance
User user = NyFactory.make<User>();
// Create with overrides
User admin = NyFactory.make<User>(overrides: {'name': 'Admin User'});
// Create with states applied
User premiumAdmin = NyFactory.make<User>(states: ['admin', 'premium']);
// Create multiple instances
List<User> users = NyFactory.create<User>(count: 5);
// Create a sequence with index-based data
List<User> numbered = NyFactory.sequence<User>(3, (int index, NyFaker faker) {
return User(name: "User ${index + 1}", email: faker.email());
});
NyFaker
NyFaker, testler için gerçekçi sahte veriler üretir. Factory tanımları içinde kullanılabilir ve doğrudan örneklendirilebilir.
NyFaker faker = NyFaker();
Kullanılabilir Metotlar
| Kategori | Metot | Dönüş Türü | Açıklama |
|---|---|---|---|
| İsimler | faker.firstName() |
String |
Rastgele ad |
faker.lastName() |
String |
Rastgele soyad | |
faker.name() |
String |
Tam isim (ad + soyad) | |
faker.username() |
String |
Kullanıcı adı | |
| İletişim | faker.email() |
String |
E-posta adresi |
faker.phone() |
String |
Telefon numarası | |
faker.company() |
String |
Şirket adı | |
| Sayılar | faker.randomInt(min, max) |
int |
Aralıkta rastgele tam sayı |
faker.randomDouble(min, max) |
double |
Aralıkta rastgele ondalık sayı | |
faker.randomBool() |
bool |
Rastgele boolean | |
| Tanımlayıcılar | faker.uuid() |
String |
UUID v4 dizesi |
| Tarihler | faker.date() |
DateTime |
Rastgele tarih |
faker.pastDate() |
DateTime |
Geçmişteki bir tarih | |
faker.futureDate() |
DateTime |
Gelecekteki bir tarih | |
| Metin | faker.lorem() |
String |
Lorem ipsum kelimeleri |
faker.sentences() |
String |
Birden fazla cümle | |
faker.paragraphs() |
String |
Birden fazla paragraf | |
faker.slug() |
String |
URL slug'ı | |
| Web | faker.url() |
String |
URL dizesi |
faker.imageUrl() |
String |
Resim URL'si (picsum.photos üzerinden) | |
faker.ipAddress() |
String |
IPv4 adresi | |
faker.macAddress() |
String |
MAC adresi | |
| Konum | faker.address() |
String |
Sokak adresi |
faker.city() |
String |
Şehir adı | |
faker.state() |
String |
ABD eyalet kısaltması | |
faker.zipCode() |
String |
Posta kodu | |
faker.country() |
String |
Ülke adı | |
| Diğer | faker.hexColor() |
String |
Hex renk kodu |
faker.creditCardNumber() |
String |
Kredi kartı numarası | |
faker.randomElement(list) |
T |
Listeden rastgele öğe | |
faker.randomElements(list, count) |
List<T> |
Listeden rastgele öğeler |
Test Önbelleği
NyTestCache, önbellekle ilgili işlevselliği test etmek için bellek içi önbellek sağlar:
nyTest('cache operations', () async {
NyTestCache cache = NyTest.cache;
// Store a value
await cache.put<String>("key", "value");
// Store with expiration
await cache.put<String>("temp", "data", seconds: 60);
// Read a value
String? value = await cache.get<String>("key");
// Check existence
bool exists = await cache.has("key");
// Clear a key
await cache.clear("key");
// Flush all
await cache.flush();
// Get cache info
int size = await cache.size();
List<String> keys = await cache.documents();
});
Platform Kanalı Mocklama
NyMockChannels, testlerin çökmemesi için yaygın platform kanallarını otomatik olarak mocklar:
void main() {
NyTest.init(); // Automatically sets up mock channels
// Or set up manually
NyMockChannels.setup();
}
Mocklanan Kanallar
- path_provider -- belgeler, geçici, uygulama desteği, kütüphane ve önbellek dizinleri
- flutter_secure_storage -- bellek içi güvenli depolama
- flutter_timezone -- saat dilimi verileri
- flutter_local_notifications -- bildirim kanalı
- sqflite -- veritabanı işlemleri
Yolları Geçersiz Kılma
NyMockChannels.overridePathProvider(
documentsPath: '/custom/documents',
temporaryPath: '/custom/temp',
);
Testlerde Güvenli Depolama
NyMockChannels.setSecureStorageValue("token", "test_abc123");
Map<String, String> storage = NyMockChannels.getSecureStorage();
NyMockChannels.clearSecureStorage();
Route Guard Mocklama
NyMockRouteGuard, gerçek kimlik doğrulama veya ağ çağrıları olmadan route guard davranışını test etmenizi sağlar. NyRouteGuard'ı genişletir ve yaygın senaryolar için factory yapıcıları sağlar.
Her Zaman Geçiren Guard
final guard = NyMockRouteGuard.pass();
Yönlendiren Guard
final guard = NyMockRouteGuard.redirect('/login');
// With additional data
final guard = NyMockRouteGuard.redirect('/error', data: {'code': 403});
Özel Mantıklı Guard
final guard = NyMockRouteGuard.custom((context) async {
if (context.data == null) {
return GuardResult.handled; // abort navigation
}
return GuardResult.next; // allow navigation
});
Guard Çağrılarını İzleme
Bir guard çağrıldıktan sonra durumunu inceleyebilirsiniz:
expect(guard.wasCalled, isTrue);
expect(guard.callCount, 1);
// Access the RouteContext from the last call
RouteContext? context = guard.lastContext;
// Reset tracking
guard.reset();
Doğrulamalar
Nylo Website özel doğrulama fonksiyonları sağlar:
Rota Doğrulamaları
expectRoute('/home'); // Assert current route
expectNotRoute('/login'); // Assert not on route
expectRouteInHistory('/home'); // Assert route was visited
expectRouteExists('/profile'); // Assert route is registered
expectRoutesExist(['/home', '/profile', '/settings']);
Durum Doğrulamaları
expectBackpackContains("key"); // Key exists
expectBackpackContains("key", value: "expected"); // Key has value
expectBackpackNotContains("key"); // Key doesn't exist
Kimlik Doğrulama Doğrulamaları
expectAuthenticated<User>(); // User is authenticated
expectGuest(); // No user authenticated
Ortam Doğrulamaları
expectEnv("APP_NAME", "MyApp"); // Env variable equals value
expectEnvSet("APP_KEY"); // Env variable is set
Mod Doğrulamaları
expectTestMode();
expectDebugMode();
expectProductionMode();
expectDevelopingMode();
API Doğrulamaları
expectApiCalled('/users');
expectApiCalled('/users', method: 'POST', times: 2);
expectApiNotCalled('/admin');
Yerel Ayar Doğrulamaları
expectLocale("en");
Toast Doğrulamaları
Bir test sırasında kaydedilen toast bildirimlerini doğrulayın. Test setUp'ınızda NyToastRecorder.setup() gerektirir:
setUp(() {
NyToastRecorder.setup();
});
nyWidgetTest('shows success toast', (tester) async {
await tester.pumpNyWidget(MyPage());
// ... trigger action that shows a toast ...
expectToastShown(id: 'success');
expectToastShown(id: 'danger', description: 'Something went wrong');
expectNoToastShown(id: 'info');
});
NyToastRecorder testler sırasında toast bildirimlerini takip eder:
// Record a toast manually
NyToastRecorder.record(id: 'success', title: 'Done', description: 'Saved!');
// Check if a toast was shown
bool shown = NyToastRecorder.wasShown(id: 'success');
// Access all recorded toasts
List<ToastRecord> toasts = NyToastRecorder.records;
// Clear recorded toasts
NyToastRecorder.clear();
Kilit ve Yükleme Doğrulamaları
NyPage/NyState widget'larında adlandırılmış kilit ve yükleme durumlarını doğrulayın:
// Assert a named lock is held
expectLocked(tester, find.byType(MyPage), 'submit');
// Assert a named lock is not held
expectNotLocked(tester, find.byType(MyPage), 'submit');
// Assert a named loading key is active
expectLoadingNamed(tester, find.byType(MyPage), 'fetchUsers');
// Assert a named loading key is not active
expectNotLoadingNamed(tester, find.byType(MyPage), 'fetchUsers');
Özel Eşleştiriciler
expect() ile özel eşleştiriciler kullanın:
// Type matcher
expect(result, isType<User>());
// Route name matcher
expect(widget, hasRouteName('/home'));
// Backpack matcher
expect(true, backpackHas("key", value: "expected"));
// API call matcher
expect(true, apiWasCalled('/users', method: 'GET', times: 1));
Durum Testi
Durum test yardımcılarını kullanarak NyPage ve NyState widget'larında EventBus tabanlı durum yönetimini test edin.
Durum Güncellemeleri Tetikleme
Normalde başka bir widget veya controller'dan gelecek durum güncellemelerini simüle edin:
// Fire an UpdateState event
fireStateUpdate('HomePageState', data: {'items': ['a', 'b']});
await tester.pump();
expect(find.text('a'), findsOneWidget);
Durum Eylemleri Tetikleme
Sayfanızda whenStateAction() tarafından işlenen durum eylemleri gönderin:
fireStateAction('HomePageState', 'refresh-page');
await tester.pump();
// With additional data
fireStateAction('CartState', 'add-item', data: {'id': 42});
await tester.pump();
Durum Doğrulamaları
// Assert a state update was fired
expectStateUpdated('HomePageState');
expectStateUpdated('HomePageState', times: 2);
// Assert a state action was fired
expectStateAction('HomePageState', 'refresh-page');
expectStateAction('CartState', 'add-item', times: 1);
// Assert on the stateData of a NyPage/NyState widget
expectStateData(tester, find.byType(MyWidget), equals(42));
NyStateTestHelpers
Tetiklenen durum güncellemelerini ve eylemlerini takip edin ve inceleyin:
// Get all updates fired to a state
List updates = NyStateTestHelpers.getUpdatesFor('MyWidget');
// Get all actions fired to a state
List actions = NyStateTestHelpers.getActionsFor('MyWidget');
// Reset all tracked state updates and actions
NyStateTestHelpers.reset();
Hata Ayıklama
dump
Mevcut test durumunu yazdırın (Backpack içeriği, auth kullanıcısı, zaman, API çağrıları, yerel ayar):
NyTest.dump();
dd (Dump and Die)
Test durumunu yazdırın ve testi hemen sonlandırın:
NyTest.dd();
Test Durumu Depolama
Test sırasında değerleri saklayın ve alın:
NyTest.set("step", "completed");
String? step = NyTest.get<String>("step");
Backpack'i Doldurmak
Backpack'i test verileriyle önceden doldurun:
NyTest.seedBackpack({
"user_name": "Anthony",
"auth_token": "test_token",
"settings": {"theme": "dark"},
});
Örnekler
Tam Test Dosyası
import 'package:flutter_test/flutter_test.dart';
import 'package:nylo_framework/nylo_framework.dart';
void main() {
NyTest.init();
nyGroup('User Authentication', () {
nyTest('can authenticate a user', () async {
NyFactory.define<User>((faker) => User(
name: faker.name(),
email: faker.email(),
));
User user = NyFactory.make<User>();
NyTest.actingAs<User>(user);
expectAuthenticated<User>();
});
nyTest('guest has no access', () async {
expectGuest();
});
});
nyGroup('API Integration', () {
nyTest('can fetch users', () async {
NyMockApi.setRecordCalls(true);
NyMockApi.respond('/api/users', {
'users': [
{'id': 1, 'name': 'Anthony'},
{'id': 2, 'name': 'Jane'},
]
});
// ... trigger API call ...
expectApiCalled('/api/users');
});
});
nyGroup('Time-Sensitive Features', () {
nyTest('subscription expires correctly', () async {
NyTest.travel(DateTime(2025, 1, 1));
// Test subscription logic at a known date
expect(NyTime.now().year, equals(2025));
NyTest.travelForward(Duration(days: 365));
expect(NyTime.now().year, equals(2026));
NyTest.travelBack();
});
});
}