When we test in Android, we test our ViewModel
. They don’t contain any View
code. This is great for testing and this way we don’t depend on Android.
Implementation
We ought to mock threads. If we don’t, we’d have to run the entire Android system and this is something we want to avoid.
Add the following to your build.gradle
file. All testImplementation
and androidTestImplementation
won’t be included in your APK.
def arch_version = "2.1.0"
testImplementation 'junit:junit:4.13'
androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.2.1"
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
// room testing
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"
// mockito
testImplementation "org.mockito:mockito-core:1.10.19"
// user interface testing
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
// test LiveData
testImplementation "androidx.arch.core:core-testing:$arch_version"
// more readable assertions
testImplementation "com.google.truth:truth:1.0.1"
mock @Inject annotations
We have the following class implementation.
class ListViewModel: ViewModel() {
@Inject
lateinit var service: Service
init {
DaggerApiComponent.create().inject(this)
}
fun refresh(something: String): Boolean {
return service.consume(something)
}
}
To check this, we have the following test class.
class ListViewModelTest {
@get:Rule
var rule = InstantTaskExecutorRule()
@Mock
lateinit var service: Service
@InjectMocks
var listViewModel = ListViewModel()
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
}
@Before
fun setUpRxSchedulers() {
// whenever a observable is called, we need to return immediately
val immediate = object : Scheduler() {
override fun scheduleDirect(run: Runnable?, delay: Long, unit: TimeUnit?): Disposable {
return super.scheduleDirect(run, 0, unit)
}
override fun createWorker(): Worker {
return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
}
}
// then we use immediate for all these scheduling
RxJavaPlugins.setInitIoSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitComputationSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitNewThreadSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitSingleSchedulerHandler { scheduler -> immediate }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> immediate }
}
@Test
fun getServiceSuccess() {
// given
val givenSomething = "test"
val expectedReturn = false
`when`(service.consume(givenSomething)).thenReturn(expectedReturn)
// when
val result = service.refresh(givenSomething)
// then
Assert.assertEquals(expectedReturn, result)
}
}
mock Thread calls
For example, we have the following static Thread
call we want to mock.
fun fetch() {
service.getCountries()
.subscribeOn(Schedulers.newThread()) // <-- call to mock
.observeOn(AndroidSchedulers.mainThread()) // <-- call to mock
.subscribeWith({ ... })
}
Then at our Test we need to create a JUnit Rule
and mock the static calls to Thread
. We need to add InstantTaskExecutorRule
. This runs all architecture components-related background jobs in the same thread so that test results happen synchronously.
class ListViewModelTest {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
@Before
fun setUpRxSchedulers() {
// whenever a observable is called, we need to return immediately
val immediate = object : Scheduler() {
override fun scheduleDirect(run: Runnable?, delay: Long, unit: TimeUnit?): Disposable {
return super.scheduleDirect(run, 0, unit)
}
override fun createWorker(): Worker {
return ExecutorScheduler.ExecutorWorker(Executor { it.run() })
}
}
// then we use immediate for all these scheduling
RxJavaPlugins.setInitIoSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitComputationSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitNewThreadSchedulerHandler { scheduler -> immediate }
RxJavaPlugins.setInitSingleSchedulerHandler { scheduler -> immediate }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> immediate }
}
}
Room Testing
Room has a special database builder named .inMemoryDatabaseBuilder()
. This allows us to create temporary databases for testing. Data won’t be persisted as the Database will be created on test start and tear down on test end.
@RunWith(AndroidJUnit4::class)
class OurDaoTest {
@get:Rule
var instantTaskExecutorRule = InstantTaskExecutorRule()
private lateinit var dao: MovieDao
private lateinit var database: TMDBDatabase
@Before
fun setUp() {
database = Room.inMemoryDatabaseBuilder(
ApplicationProvider.getApplicationContext(),
TMDBDatabase::class.java
).build()
dao = database.movieDao()
}
@After
fun tearDown() {
database.close()
}
@Test
fun saveTest() = runBlocking {
// given
val movies = listOf(
Movie(1, "test1", "path1", "date1")
Movie(2, "test2", "path2", "date2")
)
// when
dao.saveMovies(movies)
// then
val allMovies = dao.getMovies()
Truth.assertThat(allMovies).isEqualTo(movies)
}
@Test
fun deleteTest() = runBlocking {
// given
val movies = listOf(
Movie(1, "test1", "path1", "date1")
Movie(2, "test2", "path2", "date2")
)
dao.saveMovies(movies)
// when
dao.deleteAllMovies()
// then
val allMovies = dao.getMovies()
Truth.assertThat(allMovies).isEqualTo(movies)
}
}