Provider'larınızı test etme
Riverpod API'sinin temel parçalarından biri, provider'larınızı izole bir şekilde test edebilme imkânıdır.
Düzgün bir test paketi için aşılması gereken birkaç zorluk vardır:
- Testler durum paylaşmamalıdır. Yani yeni testler önceki testlerden etkilenmemelidir.
- Testler, istenen duruma ulaşabilmemiz için belirli işlevlerin sahtesini oluşturma imkânı vermelidir.
- Test ortamı, gerçek ortama mümkün olduğunca yakın olmalıdır.
Neyse ki Riverpod, bu hedeflerin tümüne ulaşmayı kolaylaştırır.
Bir test kurmak
Riverpod ile bir test tanımlarken iki ana senaryo vardır:
- Genellikle Flutter bağımlılığı olmayan birim testleri. Bu, bir provider'ın davranışını izole bir şekilde test etmek için faydalı olabilir.
- Genellikle Flutter bağımlılığı olan widget testleri. Bu, bir provider kullanan bir widget'ın davranışını test etmek için faydalı olabilir.
Birim testleri
Birim testleri, package:test paketindeki test fonksiyonu kullanılarak tanımlanır.
Diğer testlerden temel farkı, bir ProviderContainer nesnesi oluşturmak
isteyecek olmamızdır. Bu nesne, testimizin provider'larla
etkileşime geçmesini sağlar.
ProviderContainer kullanan tipik bir test şöyle görünür:
void main() {
test('Some description', () {
// Bu test için bir ProviderContainer oluşturun.
// ProviderContainer'ları testler arasında PAYLAŞMAYIN.
final container = ProviderContainer.test();
// TODO: uygulamanızı test etmek için container'ı kullanın.
expect(
container.read(provider),
equals('some value'),
);
});
}
Artık bir ProviderContainer'ımız olduğuna göre, provider'ları okumak için şunları kullanabiliriz:
container.read, bir provider'ın mevcut değerini okumak için.container.listen, bir provider'ı dinlemek ve değişikliklerden haberdar olmak için.
Provider'lar otomatik olarak yok edildiğinde container.read kullanırken dikkatli olun.
Provider'ınız dinlenmiyorsa, durumunun testinizin ortasında yok edilmesi
kuvvetle muhtemeldir.
Bu durumda container.listen kullanmayı düşünün.
Döndürdüğü değer yine de provider'ın mevcut değerini okumanızı sağlar,
ayrıca provider'ın testinizin ortasında yok edilmemesini de garanti eder:
final subscription = container.listen<String>(provider, (_, _) {});
expect(
// `container.read(provider)` ile eşdeğerdir.
// Ancak "subscription" yok edilmediği sürece provider yok edilmez.
subscription.read(),
'Some value',
);
Widget testleri
Widget testleri, package:flutter_test paketindeki testWidgets fonksiyonu kullanılarak tanımlanır.
Bu durumda, alışılmış widget testlerinden temel fark, tester.pumpWidget
çağrısının köküne bir ProviderScope widget'ı eklememiz gerektiğidir:
void main() {
testWidgets('Some description', (tester) async {
await tester.pumpWidget(
const ProviderScope(child: YourWidgetYouWantToTest()),
);
});
}
Bu, Flutter uygulamamızda Riverpod'u etkinleştirirken yaptığımıza benzer.
Ardından, widget'ımızla etkileşime geçmek için testerı kullanabiliriz.
Alternatif olarak provider'larla etkileşime geçmek isterseniz,
bir ProviderContainer elde edebilirsiniz.
Bunu tester.container() ile elde etmek mümkündür.
Böylece tester sayesinde şunu yazabiliriz:
final container = tester.container();
Daha sonra bunu provider'ları okumak için kullanabiliriz. İşte tam bir örnek:
void main() {
testWidgets('Some description', (tester) async {
await tester.pumpWidget(
const ProviderScope(child: YourWidgetYouWantToTest()),
);
final container = tester.container();
// TODO provider'larınızla etkileşime geçin
expect(
container.read(provider),
'some value',
);
});
}
Provider'ların sahtesini oluşturma
Şimdiye kadar bir testin nasıl kurulacağını ve provider'larla temel etkileşimleri gördük. Ancak bazı durumlarda bir provider'ın sahtesini oluşturmak isteyebiliriz.
İşin güzel yanı: Tüm provider'ların sahtesi, ek bir kuruluma gerek kalmadan varsayılan olarak oluşturulabilir.
Bu, ProviderScope veya ProviderContainer üzerindeki overrides parametresi
belirtilerek mümkündür.
Aşağıdaki provider'ı ele alalım:
- riverpod
- riverpod_generator
// Önden başlatılan (eager initialization) bir provider.
final exampleProvider = FutureProvider<String>((ref) async => 'Hello world');
// Önden başlatılan (eager initialization) bir provider.
Future<String> example(Ref ref) async => 'Hello world';
Sahtesini şu şekilde oluşturabiliriz:
// Birim testlerinde, daha önceki "createContainer" yardımcımızı yeniden kullanarak.
final container = ProviderContainer.test(
// Sahtesini oluşturacağımız provider'ların listesini belirtebiliriz:
overrides: [
// Bu durumda "exampleProvider"ın sahtesini oluşturuyoruz.
exampleProvider.overrideWith((ref) {
// Bu fonksiyon, bir provider'ın tipik başlatma fonksiyonudur.
// Normalde "ref.watch" çağırdığınız ve başlangıç durumunu döndürdüğünüz yer burasıdır.
// Varsayılan "Hello world" değerini özel bir değerle değiştirelim.
// Ardından `exampleProvider` ile etkileşime geçmek bu değeri döndürecektir.
return 'Hello from tests';
}),
],
);
// Aynı şeyi widget testlerinde ProviderScope kullanarak da yapabiliriz:
await tester.pumpWidget(
ProviderScope(
// ProviderScope'lar da birebir aynı "overrides" parametresine sahiptir
overrides: [
// Öncekiyle aynı
exampleProvider.overrideWith((ref) => 'Hello from tests'),
],
child: const YourWidgetYouWantToTest(),
),
);
Bir provider'daki değişiklikleri gözetleme
Testlerimizde bir ProviderContainer elde ettiğimize göre, bunu bir provider'ı
"dinlemek" için kullanabiliriz:
container.listen<String>(
provider,
(previous, next) {
print('The provider changed from $previous to $next');
},
);
Bunu daha sonra mockito
veya mocktail gibi paketlerle birleştirip verify API'lerini kullanabilirsiniz.
Ya da daha basitçe, tüm değişiklikleri bir listeye ekleyip liste üzerinde doğrulama yapabilirsiniz.
Asenkron provider'ları beklemek
Riverpod'da provider'ların bir Future/Stream döndürmesi çok yaygındır.
Bu durumda, testlerimizin bu asenkron işlemin tamamlanmasını beklemesi
gerekecektir.
Bunu yapmanın bir yolu, bir provider'ın .futureunu okumaktır:
// TODO: uygulamanızı test etmek için container'ı kullanın.
// Beklentimiz asenkron olduğu için "expectLater" kullanmalıyız
await expectLater(
// "provider" yerine "provider.future"ı okuyoruz.
// Bu, asenkron provider'larda mümkündür ve provider'ın değeriyle
// sonuçlanacak bir Future döndürür.
container.read(provider.future),
// Future'ın beklenen değerle sonuçlandığını doğrulayabiliriz.
// Alternatif olarak hatalar için "throwsA" kullanabiliriz.
completion('some value'),
);
Notifier'ların sahtesini oluşturma
Notifier'ların sahtesini oluşturmak genel olarak önerilmez. Bunun nedeni, Notifier'ların tek başlarına örneklenememesi ve yalnızca bir Provider'ın parçası olarak kullanıldıklarında çalışmalarıdır.
Bunun yerine, muhtemelen Notifier'ınızın mantığına bir soyutlama katmanı eklemeli ve o soyutlamanın sahtesini oluşturmalısınız. Örneğin, bir Notifier'ın sahtesini oluşturmak yerine, Notifier'ın veri çekmek için kullandığı bir "repository"nin sahtesini oluşturabilirsiniz.
Yine de bir Notifier'ın sahtesini oluşturmakta ısrar ediyorsanız, böyle bir sahte oluştururken dikkat edilmesi gereken özel bir nokta vardır: Sahteniz, orijinal Notifier temel sınıfından türemelidir. Notifier'ı "implement" edemezsiniz, çünkü bu arayüzü bozar.
Bu nedenle, bir Notifier'ın sahtesini oluştururken aşağıdaki mockito kodunu yazmak yerine:
class MyNotifierMock with Mock implements MyNotifier {}
Şunu yazmalısınız:
- riverpod
- riverpod_generator
class MyNotifier extends Notifier<int> {
int build() => throw UnimplementedError();
}
// Sahteniz, notifier'ınızın kullandığı Notifier temel sınıfından
// türemek zorundadır
class MyNotifierMock extends Notifier<int> with Mock implements MyNotifier {}
class MyNotifier extends _$MyNotifier {
int build() => throw UnimplementedError();
}
// Sahteniz, notifier'ınızın kullandığı Notifier temel sınıfından
// türemek zorundadır
class MyNotifierMock extends _$MyNotifier with Mock implements MyNotifier {}
Kod üretimi kullanıyorsanız, yukarıdakinin çalışması için sahtenizin,
sahtesini oluşturduğunuz Notifier ile aynı dosyaya yerleştirilmesi gerekir.
Aksi takdirde _$MyNotifier sınıfına erişiminiz olmaz.
Ardından, notifier'ınızı kullanmak için şunu yapabilirsiniz:
void main() {
test('Some description', () {
final container = ProviderContainer.test(
// Sahte Notifier'ımızı oluşturması için provider'ı geçersiz kılın.
overrides: [myProvider.overrideWith(MyNotifierMock.new)],
);
// Ardından sahte notifier'ı container üzerinden elde edin:
final notifier = container.read(myProvider.notifier);
// Daha sonra notifier ile gerçeğiyle yapacağınız gibi etkileşime geçebilirsiniz:
notifier.state = 42;
});
}