series

  • Introduction to Android Test
  • How to Test ViewModel and LiveData
  • How to Test Doubles as a Repository

preface

Development that cannot test is not good development — Lu Xun


For a long time, there are few content resources about how to write test code. I saw the video of this part in Youda University before, but it may not be friendly to some friends because there is no Chinese subtitles. So I decided to put this together in a series of articles. This is the third in a series of articles. Earlier we looked at how to test ViewModel and LiveData


Udacity Advanced Android with Kotlin-Lesson 11-5.2 Testing: Intro to Test Doubles & Dependency Injection

Problems with testing Repository

When you write unit tests for a class, you only want to test the code for that class. The tricky part about testing Repository is that we only want to test the code in Repository and not the code underneath it


Let’s take a quick look at the Repository code in our demo


Obviously, we cannot test Repository without testing the code in the RepoDataSource

You may be wondering why it is important to be able to test the Repository code separately when writing unit tests. Here are some reasons

  • Some of the Repository code depends on other code, such as database code that may need to run on a real device

  • Repository relies on code such as database code or network data code that takes a while to run, and network requests may even fail

  • There are bugs in the Repository dependent code that can cause tests to fail, but because we are doing unit tests for Repository, you cannot locate them

Test Repository we want it to run fast, we need local test

The code for the database or network requests that Repository relies on is long-running and flaky test, which means your tests are unreliable

To put it simply, Flaky Tests are when you run the same code repeatedly, sometimes it will pass, sometimes it won’t

This should be avoided when testing because the results are unreliable

So how do we solve this problem? The answer is Test Double

The concept of Test Doubles

A Test Double is a carefully prepared class for testing that replaces the real version of the data in the Test. It’s like when a stunt double takes over for an actor in a movie. So in Repository, we can make a Test Double for the data source

In fact, there are many kinds of Test doubles, and this series of articles will cover Fake and Mock


Fake A valid implementation of the class is suitable only for testing, not production
Mock Used to track method calls and determine whether a test has passed based on whether the method was called correctly
Stub Contains no logic and returns only the logic returned by the developer’s programming
Dummy Used for passing but not being used, for example as a parameter only
Spy Some additional information can be tracked; For example, if you create SpyTaskRepository, it might keep track of how many times the addTask method is called


For more information on Test Double, go to Testing on the Toilet: Know Your Test Doubles

For Test Doubles in Android, see Great Tips about using Test Doubles


Using Fake means that the data source is not fetched from the network or database, so it is only suitable for testing

Test the Repository

We can replace the LocalDataSource and RemoteDataSource with the FakeDataSource

First we create a FakeDataSource in the test Source set and implement the RepoDataSource interface

This gives the interface three implementation classes


We pass the Repo List in the constructor and complete its internal fetch and save methods


We can then write the test code for RepoRepository

Call Generate on RepoRepository and select the Create Test option, thus creating the RepoRepositoryTest

First we need to provide the data source

class RepoRepositoryTest {
    private val repo1 = Repo(id = 1, fork = false)
    private val repo2 = Repo(id = 2, fork = false)
    private val repo3 = Repo(id = 3, fork = true)
    private val repo4 = Repo(id = 4, fork = true)

    private val remoteRepos = listOf(repo1, repo2)
    private val localRepos = listOf(repo3, repo4)
    / /...
}
Copy the code

We then declare RepoRepository localDataSource and remoteDataSource

class RepoRepositoryTest {    
    / /...
 private lateinit var localReposDataSource: FakeDataSource
    private lateinit var remoteReposDataSource: FakeDataSource

    private lateinit var repoRepository: RepoRepository
    / /...
}
Copy the code

We then write the code that initializes Repository

@Before
fun initRepository(a) {
    remoteReposDataSource = FakeDataSource(remoteRepos)
    localReposDataSource = FakeDataSource(localRepos)

    repoRepository = RepoRepository(remoteReposDataSource, localReposDataSource)
}
Copy the code

Finally, we write the Test code. Since getRepos is a suspend function, we use runBlocking{} here.

@Test
fun getRepos(a) = runBlocking {
    val result = repoRepository.getRepos("Flywith24".true)
    assertThat(result.value, IsEqual(remoteRepos))
}
Copy the code

About me

I am a Fly_with24

  • The Denver nuggets

  • Jane’s book

  • Github