Android Unit Testing

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)
	}

}