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