Android'de Kotlin eş yordamlarını test etme

Eşzamanlı yürütme işlemleri eşzamansız olabileceği ve birden fazla iş parçacığında gerçekleşebileceği için, eş yordamları kullanan birim testi kodları biraz daha dikkatli olmayı gerektirir. Bu kılavuzda, askıya alma işlevlerinin nasıl test edilebileceği, bilmeniz gereken test yapıları ve eş yordamların kullanıldığı kodunuzu nasıl test edilebilir hale getireceğiniz açıklanmaktadır.

Bu kılavuzda kullanılan API'ler kotlinx.coroutines.test kitaplığının bir parçasıdır. Bu API'lere erişebilmek için projenize bir test bağımlılığı olarak yapıyı eklediğinizden emin olun.

dependencies {
    testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutines_version"
}

Testlerde askıya alma işlevlerini çağırma

Testlerde askıya alma işlevlerini çağırmak için eş yordam içinde olmanız gerekir. JUnit test işlevlerinin kendisi askıya alınmadığından, yeni bir eş yordam başlatmak için testlerinizin içinde bir eş yordam oluşturucu çağırmanız gerekir.

runTest, test için tasarlanmış bir eş yordam oluşturucudur. Eş yordam içeren tüm testleri sarmalamak için bunu kullanın. Eş yordamların yalnızca doğrudan test gövdesinde değil, aynı zamanda testte kullanılan nesneler tarafından da başlatılabileceğini unutmayın.

suspend fun fetchData(): String {
    delay(1000L)
    return "Hello world"
}

@Test
fun dataShouldBeHelloWorld() = runTest {
    val data = fetchData()
    assertEquals("Hello world", data)
}

Genel olarak, test başına bir runTest çağrınız olmalıdır ve bir ifade gövdesi kullanmanız önerilir.

Testinizin kodunu runTest içinde sarmalamak temel askıya alma işlevlerini test etmek için işe yarar ve eş yordamlardaki gecikmeleri otomatik olarak atlayarak yukarıdaki testin bir saniyeden çok daha hızlı tamamlanmasını sağlar.

Bununla birlikte, test edilen kodunuzda ne olduğuna bağlı olarak dikkate alınması gereken başka noktalar da vardır:

  • Kodunuz, runTest tarafından oluşturulan üst düzey test koordini dışında yeni eş yordamlar oluşturduğunda, uygun TestDispatcher öğesini seçerek bu yeni eş yordamların nasıl planlanacağını kontrol etmeniz gerekir.
  • Kodunuz, eş yordam yürütmeyi diğer sevk görevlilerine taşırsa (örneğin, withContext kullanarak) runTest genellikle çalışmaya devam eder, ancak artık gecikmeler atlanmaz ve kod birden fazla iş parçacığında çalıştırıldığından testler daha az tahmin edilebilir olur. Bu nedenlerle, testlerde gerçek sevk görevlilerinin yerine test görev dağıtıcıları eklemeniz gerekir.

Test Görevlileri

TestDispatchers, test amaçlı CoroutineDispatcher uygulamalarıdır. Test sırasında yeni eş yordamlar oluşturulursa yeni eş yordamların yürütülmesini öngörülebilir hale getirmek için TestDispatchers kullanmanız gerekir.

Kullanılabilir iki TestDispatcher uygulaması vardır: StandardTestDispatcher ve UnconfinedTestDispatcher, yeni başlatılan eş yordamların farklı programlarını gerçekleştirir. Bu ikisi de sanal zamanı kontrol etmek ve testte çalışan eş yordamları yönetmek için bir TestCoroutineScheduler kullanır.

Bir testte kullanılan yalnızca bir planlayıcı örneği olmalıdır ve tüm TestDispatchers arasında paylaşılanlar olmalıdır. Paylaşım planlayıcıları hakkında bilgi edinmek için TestDispatcher Ekleme bölümüne bakın.

Üst düzey test koordinini başlatmak için runTest, her zaman TestDispatcher kullanacak olan bir CoroutineScope uygulaması olan bir TestScope oluşturur. Belirtilmezse TestScope, varsayılan olarak bir StandardTestDispatcher oluşturur ve üst düzey test koordinini çalıştırmak için bunu kullanır.

runTest, TestScope sevk görevlisi tarafından kullanılan planlayıcıda sıraya alınan eş yordamları takip eder ve ilgili planlayıcı üzerinde bekleyen çalışma olduğu sürece geri dönmez.

StandartTest Görevlisi

StandardTestDispatcher üzerinde yeni eş yordamlar başlattığınızda, test iş parçacığının ücretsiz olduğu her durumda çalıştırılmak üzere temel planlayıcıda sıraya alınırlar. Bu yeni eş yordamların çalışmasına izin vermek için test iş parçacığını getirmeniz gerekir (diğer eş yordamların kullanılabilmesi için serbest bırakın). Bu sıraya alma davranışı, yeni eş yordamların test sırasında çalışma şekli üzerinde size hassas bir kontrol sağlar ve üretim kodundaki eş yordamların programlanmasına benzer.

Üst düzey test eş yordamının yürütülmesi sırasında test iş parçacığı hiç oluşturulmazsa yeni eş yordamlar yalnızca test eş yordamı tamamlandıktan sonra (ancak runTest döndürmeden önce) çalışır:

@Test
fun standardTest() = runTest {
    val userRepo = UserRepository()

    launch { userRepo.register("Alice") }
    launch { userRepo.register("Bob") }

    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails
}

Sıraya alınan eş yordamların çalışmasını sağlamak için test eş yordamını oluşturmanın çeşitli yolları vardır. Bu çağrıların tümü, geri dönmeden önce diğer eş yordamların test ileti dizisinde çalıştırılmasına izin verir:

  • advanceUntilIdle: Sırada hiçbir şey kalmayıncaya kadar planlayıcıdaki diğer tüm eş yordamları çalıştırır. Bu, beklemedeki tüm eş yordamların çalıştırılmasına izin vermek için iyi bir varsayılan seçenektir ve çoğu test senaryosunda çalışır.
  • advanceTimeBy: Sanal zamanı belirli bir miktar kadar ilerletir ve bu noktadan önce çalışacak şekilde planlanmış eş yordamları sanal zamanda çalıştırır.
  • runCurrent: Geçerli sanal zamanda planlanmış eş yordamlar çalıştırır.

Önceki testi düzeltmek amacıyla advanceUntilIdle, bekleyen iki eş yordamın onaylamaya devam etmeden önce işlerini yapmasına izin vermek için kullanılabilir:

@Test
fun standardTest() = runTest {
    val userRepo = UserRepository()

    launch { userRepo.register("Alice") }
    launch { userRepo.register("Bob") }
    advanceUntilIdle() // Yields to perform the registrations

    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
}

Serbest Test Görevlisi

UnconfinedTestDispatcher üzerinde yeni eş yordamlar başlatıldığında, istekle mevcut ileti dizisinde başlatılır. Bu sayede, eş yordam oluşturucunun geri dönmesini beklemeden hemen çalışmaya başlayacaklar. Yeni eş yordamların çalıştırılmasına izin vermek için test iş parçacığını manuel olarak oluşturmanız gerekmediğinden çoğu durumda bu gönderme davranışı daha basit test koduyla sonuçlanır.

Ancak bu davranış, test dışı sevk görevlileriyle üretimde göreceğinizden farklıdır. Testiniz eşzamanlılığa odaklanıyorsa bunun yerine StandardTestDispatcher özelliğini kullanmayı tercih edin.

Bu sevk görevlisini, varsayılan test yerine runTest konumundaki üst düzey test eş yordamı için kullanmak istiyorsanız bir örnek oluşturun ve bunu parametre olarak iletin. Bu işlem, sevk görevlisini TestScope kaynağından devraldığı için runTest içinde oluşturulan yeni eş yordamların istekli bir şekilde yürütülmesini sağlar.

@Test
fun unconfinedTest() = runTest(UnconfinedTestDispatcher()) {
    val userRepo = UserRepository()

    launch { userRepo.register("Alice") }
    launch { userRepo.register("Bob") }

    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ✅ Passes
}

Bu örnekte, lansman çağrıları yeni eş yordamlarını UnconfinedTestDispatcher saatinde heyecanla başlatacaktır; yani her lansman çağrısı yalnızca kayıt tamamlandıktan sonra geri gelecektir.

UnconfinedTestDispatcher adlı kanalın yeni eş yordamları heyecanla başlatacağını, ancak bu, onları da sabırsızlıkla tamamlayacağı anlamına gelmez. Yeni eş yordam askıya alınırsa diğer eş yordamlar yürütmeye devam eder.

Örneğin, bu testte kullanıma sunulan yeni eş yordam Aylin'i kaydeder ancak delay çağrıldığında askıya alınır. Bu işlem, üst düzey koordinasyonun onaylamaya devam etmesini sağlar ve Bülent henüz kaydedilmemiş olduğundan test başarısız olur:

@Test
fun yieldingTest() = runTest(UnconfinedTestDispatcher()) {
    val userRepo = UserRepository()

    launch {
        userRepo.register("Alice")
        delay(10L)
        userRepo.register("Bob")
    }

    assertEquals(listOf("Alice", "Bob"), userRepo.getAllUsers()) // ❌ Fails
}

Test sevk görevlileri ekleme

Test edilen kod, ileti dizilerini değiştirmek (withContext ile) veya yeni eş yordamlar başlatmak için sevk görevlilerini kullanabilir. Kod, paralel olarak birden fazla iş parçacığında yürütüldüğünde testler güvenilir olmayabilir. Onaylama işlemlerini doğru zamanda gerçekleştirmek veya kontrolünüzün olmadığı arka plan iş parçacıklarında çalışan görevlerin tamamlanmasını beklemek zor olabilir.

Testlerde, bu sevk görevlilerini TestDispatchers örnekleriyle değiştirin. Bunun bazı avantajları vardır:

  • Kod, tek test iş parçacığı üzerinde çalışarak testleri daha deterministik hale getirir
  • Yeni eş yordamların nasıl planlanıp yürütüleceğini kontrol edebilirsiniz
  • TestDispatcher araçları, gecikmeleri otomatik olarak atlayan ve manuel olarak ilerlemenizi sağlayan sanal zaman için bir planlayıcı kullanır.

Bağımlılık yerleştirme yöntemini kullanarak görev dağıtıcıların yerini alacak gerçek sevk görevlilerini testler. Bu örneklerde CoroutineDispatcher ekleyeceğiz, ancak dilerseniz en geniş kapsamlı ifade CoroutineContext Böylece testler sırasında daha da fazla esneklik elde edebilirsiniz.

Eş yordamların başlatıldığı sınıflara CoroutineScope da ekleyebilirsiniz. (Kapsam ekleme bölümünde açıklandığı gibi) ilgili görev dağıtıcı yerine bölümüne ekleyin.

TestDispatchers, örneklendirildiğinde varsayılan olarak yeni bir planlayıcı oluşturur. runTest içinde TestScope öğesinin testScheduler özelliğine erişebilir ve bunu yeni oluşturulan TestDispatchers öğelerine aktarabilirsiniz. Böylece sanal zamana dair anlayışları paylaşılacak ve advanceUntilIdle gibi yöntemler, testin tamamlanması için tüm test görev dağıtıcılarında eş yordamlar çalıştırır.

Aşağıdaki örnekte, initialize yönteminde IO sevk aracını kullanarak yeni bir eş yordam oluşturan ve çağrıyı fetchData yönteminde IO sevk görevlisine geçiren bir Repository sınıfı görebilirsiniz:

// Example class demonstrating dispatcher use cases
class Repository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
    private val scope = CoroutineScope(ioDispatcher)
    val initialized = AtomicBoolean(false)

    // A function that starts a new coroutine on the IO dispatcher
    fun initialize() {
        scope.launch {
            initialized.set(true)
        }
    }

    // A suspending function that switches to the IO dispatcher
    suspend fun fetchData(): String = withContext(ioDispatcher) {
        require(initialized.get()) { "Repository should be initialized first" }
        delay(500L)
        "Hello world"
    }
}

Testlerde, IO sevk görevlisinin yerine bir TestDispatcher uygulaması ekleyebilirsiniz.

Aşağıdaki örnekte, depoya StandardTestDispatcher yerleştiriyoruz ve initialize ürününde başlayan yeni eş yordamın devam etmeden önce tamamlandığından emin olmak için advanceUntilIdle kodunu kullanıyoruz.

fetchData, test iş parçacığında çalıştırılacağı ve test sırasında içerdiği gecikmeyi atlanacağı için TestDispatcher üzerinde çalıştırmanın avantajlarından da faydalanır.

class RepositoryTest {
    @Test
    fun repoInitWorksAndDataIsHelloWorld() = runTest {
        val dispatcher = StandardTestDispatcher(testScheduler)
        val repository = Repository(dispatcher)

        repository.initialize()
        advanceUntilIdle() // Runs the new coroutine
        assertEquals(true, repository.initialized.get())

        val data = repository.fetchData() // No thread switch, delay is skipped
        assertEquals("Hello world", data)
    }
}

TestDispatcher üzerinde başlatılan yeni eş yordamlar, yukarıda initialize ile gösterildiği gibi manuel olarak gelişmiş olabilir. Bununla birlikte, üretim kodunda bunun mümkün veya istenen bir durum olmadığını unutmayın. Bunun yerine, bu yöntem askıya alınacak (sıralı yürütme için) veya bir Deferred değeri döndürecek (eş zamanlı yürütme için) şekilde yeniden tasarlanmalıdır.

Örneğin, yeni bir koordinat başlatmak ve bir Deferred oluşturmak için async aracını kullanabilirsiniz:

class BetterRepository(private val ioDispatcher: CoroutineDispatcher = Dispatchers.IO) {
    private val scope = CoroutineScope(ioDispatcher)

    fun initialize() = scope.async {
        // ...
    }
}

Böylece, hem testlerde hem de üretim kodunda bu kodu tamamladıktan sonra güvenle await yapabilirsiniz:

@Test
fun repoInitWorks() = runTest {
    val dispatcher = StandardTestDispatcher(testScheduler)
    val repository = BetterRepository(dispatcher)

    repository.initialize().await() // Suspends until the new coroutine is done
    assertEquals(true, repository.initialized.get())
    // ...
}

Eş yordamlar, planlayıcıyı paylaştığı bir TestDispatcher üzerindeyse runTest, geri dönmeden önce beklemedeki eş yordamların tamamlanmasını bekler. Ayrıca, diğer görev dağıtıcılarda olsalar bile üst düzey test eş yordamının alt öğeleri olan eş yordamları da bekler (dispatchTimeoutMs parametresi tarafından belirtilen, varsayılan olarak 60 saniye olan bir zaman aşımına kadar).

Ana sevk görevlisini ayarlama

Yerel birim testlerinde, Android kullanıcı arayüzü iş parçacığını sarmalayan Main görev dağıtıcısı, bu testler Android cihazda değil yerel bir JVM'de yürütüldüğünden kullanılamayacaktır. Test edilen kodunuz ana iş parçacığına referans veriyorsa birim testleri sırasında bir istisna oluşturur.

Bazı durumlarda, Main sevk görevlisini diğer görev dağıtıcılarla aynı şekilde yerleştirebilirsiniz (önceki bölümde açıklandığı gibi). Böylece, testlerde onu bir TestDispatcher ile değiştirebilirsiniz. Ancak, viewModelScope gibi bazı API'ler arka planda sabit kodlu bir Main görev dağıtıcısı kullanır.

Aşağıda, verileri yükleyen bir eş yordam başlatmak için viewModelScope kullanan bir ViewModel uygulaması örneği verilmiştir:

class HomeViewModel : ViewModel() {
    private val _message = MutableStateFlow("")
    val message: StateFlow<String> get() = _message

    fun loadMessage() {
        viewModelScope.launch {
            _message.value = "Greetings!"
        }
    }
}

Main sevk görevlisini her durumda TestDispatcher ile değiştirmek için Dispatchers.setMain ve Dispatchers.resetMain işlevlerini kullanın.

class HomeViewModelTest {
    @Test
    fun settingMainDispatcher() = runTest {
        val testDispatcher = UnconfinedTestDispatcher(testScheduler)
        Dispatchers.setMain(testDispatcher)

        try {
            val viewModel = HomeViewModel()
            viewModel.loadMessage() // Uses testDispatcher, runs its coroutine eagerly
            assertEquals("Greetings!", viewModel.message.value)
        } finally {
            Dispatchers.resetMain()
        }
    }
}

Main sevk görevlisi bir TestDispatcher ile değiştirildiyse yeni oluşturulan tüm TestDispatchers, Main görev dağıtıcısının planlayıcısını otomatik olarak kullanacaktır. Buna, runTest tarafından oluşturulan StandardTestDispatcher görev dahildir (başka bir görev dağıtıcı geçmiyorsa).

Bu, test sırasında yalnızca tek bir planlayıcının kullanılmasını kolaylaştırır. Bunun işe yaraması için Dispatchers.setMain çağrısından sonra diğer tüm TestDispatcher örneklerini oluşturduğunuzdan emin olun.

Her testte Main sevk görevlisinin yerini alan kodun yinelenmesini önlemek için yaygın olarak kullanılan bir kalıp, kodu bir JUnit test kuralına ayıklamaktır:

// Reusable JUnit4 TestRule to override the Main dispatcher
class MainDispatcherRule(
    val testDispatcher: TestDispatcher = UnconfinedTestDispatcher(),
) : TestWatcher() {
    override fun starting(description: Description) {
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description) {
        Dispatchers.resetMain()
    }
}

class HomeViewModelTestUsingRule {
    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    @Test
    fun settingMainDispatcher() = runTest { // Uses Main’s scheduler
        val viewModel = HomeViewModel()
        viewModel.loadMessage()
        assertEquals("Greetings!", viewModel.message.value)
    }
}

Bu kural uygulaması varsayılan olarak bir UnconfinedTestDispatcher kullanır, ancak Main sevk görevlisinin belirli bir test sınıfında hızlı bir şekilde yürütülmemesi gerekiyorsa parametre olarak StandardTestDispatcher eklenebilir.

Test gövdesinde bir TestDispatcher örneğine ihtiyaç duyduğunuzda, istenen türde olduğu sürece kuraldaki testDispatcher öğesini yeniden kullanabilirsiniz. Testte kullanılan TestDispatcher türünü açıkça belirtmek isterseniz veya Main için kullanılandan farklı bir TestDispatcher türüne ihtiyacınız varsa runTest içinde yeni bir TestDispatcher oluşturabilirsiniz. Main sevk görevlisi TestDispatcher olarak ayarlandığından, yeni oluşturulan her TestDispatchers planlayıcısını otomatik olarak paylaşır.

class DispatcherTypesTest {
    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    @Test
    fun injectingTestDispatchers() = runTest { // Uses Main’s scheduler
        // Use the UnconfinedTestDispatcher from the Main dispatcher
        val unconfinedRepo = Repository(mainDispatcherRule.testDispatcher)

        // Create a new StandardTestDispatcher (uses Main’s scheduler)
        val standardRepo = Repository(StandardTestDispatcher())
    }
}

Testin dışında sevk görevlileri oluşturma

Bazı durumlarda, test yönteminin dışında bir TestDispatcher aracının kullanılabilir olması gerekebilir. Örneğin, test sınıfındaki bir özellik başlatılırken:

class ExampleRepository(private val ioDispatcher: CoroutineDispatcher) { /* ... */ }

class RepositoryTestWithRule {
    private val repository = ExampleRepository(/* What TestDispatcher? */)

    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    @Test
    fun someRepositoryTest() = runTest {
        // Test the repository...
        // ...
    }
}

Main sevk görevlisini önceki bölümde gösterildiği gibi değiştiriyorsanız Main sevk görevlisi değiştirildikten sonra oluşturulan TestDispatchers, planlayıcısını otomatik olarak paylaşır.

Ancak test sınıfının özellikleri olarak oluşturulan TestDispatchers veya test sınıfındaki özelliklerin ilk kullanıma hazırlanması sırasında oluşturulan TestDispatchers için bu durum geçerli değildir. Bunlar, Main sevk görevlisi değiştirilmeden önce başlatılır. Bu nedenle yeni planlayıcılar oluştururlar.

Testinizde yalnızca bir planlayıcı olduğundan emin olmak için önce MainDispatcherRule özelliğini oluşturun. Ardından, sınıf düzeyindeki diğer özelliklerin başlatıcılarında gerektiğinde sevk görevlisini (veya farklı türde bir TestDispatcher öğesine ihtiyacınız varsa planlayıcısını) yeniden kullanın.

class RepositoryTestWithRule {
    @get:Rule
    val mainDispatcherRule = MainDispatcherRule()

    private val repository = ExampleRepository(mainDispatcherRule.testDispatcher)

    @Test
    fun someRepositoryTest() = runTest { // Takes scheduler from Main
        // Any TestDispatcher created here also takes the scheduler from Main
        val newTestDispatcher = StandardTestDispatcher()

        // Test the repository...
    }
}

Testte oluşturulan runTest ve TestDispatchers işlemlerinin yine de Main sevk görevlisinin planlayıcısını otomatik olarak paylaşacağını unutmayın.

Main sevk görevlisini değiştirmiyorsanız sınıfın özelliği olarak ilk TestDispatcher öğenizi (yeni bir planlayıcı oluşturur) oluşturun. Ardından, bu planlayıcıyı hem özellik olarak hem de test kapsamında her runTest çağrısına ve oluşturulan her yeni TestDispatcher öğesine manuel olarak iletin:

class RepositoryTest {
    // Creates the single test scheduler
    private val testDispatcher = UnconfinedTestDispatcher()
    private val repository = ExampleRepository(testDispatcher)

    @Test
    fun someRepositoryTest() = runTest(testDispatcher.scheduler) {
        // Take the scheduler from the TestScope
        val newTestDispatcher = UnconfinedTestDispatcher(this.testScheduler)
        // Or take the scheduler from the first dispatcher, they’re the same
        val anotherTestDispatcher = UnconfinedTestDispatcher(testDispatcher.scheduler)

        // Test the repository...
    }
}

Bu örnekte, ilk sevk görevlisinin planlayıcısı runTest adresine iletilir. Bu işlem, söz konusu planlayıcıyı kullanarak TestScope için yeni bir StandardTestDispatcher oluşturur. Ayrıca, sevk görevlisini doğrudan runTest hattına geçerek ilgili sevk görevlisinde test eş yordamını çalıştırabilirsiniz.

Kendi TestScope'unuzu oluşturma

TestDispatchers ürününde olduğu gibi, test gövdesinin dışında bir TestScope öğesine erişmeniz gerekebilir. runTest, arka planda otomatik olarak bir TestScope oluştururken runTest ile kullanmak üzere kendi TestScope öğenizi de oluşturabilirsiniz.

Bu işlemi yaparken, oluşturduğunuz TestScope üzerinde runTest adlı kişiyi çağırdığınızdan emin olun:

class SimpleExampleTest {
    val testScope = TestScope() // Creates a StandardTestDispatcher

    @Test
    fun someTest() = testScope.runTest {
        // ...
    }
}

Yukarıdaki kod, dolaylı olarak TestScope için bir StandardTestDispatcher ve yeni bir planlayıcı oluşturur. Bu nesnelerin tümü açıkça oluşturulabilir. Bu özellik, bağımlı ekleme kurulumlarıyla entegre etmeniz gerektiğinde yararlı olabilir.

class ExampleTest {
    val testScheduler = TestCoroutineScheduler()
    val testDispatcher = StandardTestDispatcher(testScheduler)
    val testScope = TestScope(testDispatcher)

    @Test
    fun someTest() = testScope.runTest {
        // ...
    }
}

Kapsam yerleştirme

sırasında kontrol etmeniz gereken, eş yordamlar oluşturan bir sınıfınız varsa söz konusu sınıfa eş yordamlı bir kapsam ekleyebilir, bunu Testlerde TestScope.

Aşağıdaki örnekte UserState sınıfı bir UserRepository öğesine bağlıdır yeni kullanıcılar kaydettirmek ve kayıtlı kullanıcıların listesini getirmek için kullanılır. Bu çağrılar UserRepository işlevi, işlev çağrılarını askıya alıyor. UserState, yerleştirilen registerUser işlevi içinde yeni bir eş yordam başlatmak için CoroutineScope tuşuna basın.

class UserState(
    private val userRepository: UserRepository,
    private val scope: CoroutineScope,
) {
    private val _users = MutableStateFlow(emptyList<String>())
    val users: StateFlow<List<String>> = _users.asStateFlow()

    fun registerUser(name: String) {
        scope.launch {
            userRepository.register(name)
            _users.update { userRepository.getAllUsers() }
        }
    }
}

Bu sınıfı test etmek için, ders oluştururken runTest sınıfından TestScope notunu geçebilirsiniz. UserState nesnesi:

class UserStateTest {
    @Test
    fun addUserTest() = runTest { // this: TestScope
        val repository = FakeUserRepository()
        val userState = UserState(repository, scope = this)

        userState.registerUser("Mona")
        advanceUntilIdle() // Let the coroutine complete and changes propagate

        assertEquals(listOf("Mona"), userState.users.value)
    }
}

Test işlevinin dışına, örneğin bir özellik olarak oluşturulan bir test için Kendi TestScope'unuzu oluşturma.

Ek kaynaklar