SlideShare a Scribd company logo
1 of 50
Android Unit Test
Bui Huu Phuoc
HCMC - Vietnam, 24/07/2020
1
Table of contents
- What’s Android testing?
- Fundamentals of Testing
- Unit tests
- Write unit tests for Clean Architecture
- Test coverage
- References
2
What’s Android testing?
- It’s a part of the app development process.
- Verify your app's correctness, functional behavior, and usability
before you release it publicly.
- Rapid feedback on failures.
- Early failure detection in the development cycle.
- Safer code refactoring, letting you optimize code without worrying about regressions.
- Stable development velocity, helping you minimize technical debt.
3
Fundamentals of Testing
- Organize your code for testing
- Configure your test environment
- Write your tests
4
Fundamentals of Testing →
Organize your code for testing
- Create and test code iteratively
5
Fundamentals of Testing →
Organize your code for testing
- View your app as a series of modules
6
Fundamentals of Testing →
Configure your test environment
- Organize test directories based on execution environment
- androidTest: Directory should contain the tests that run on real or virtual devices.
- Integration tests
- End-to-end tests
- Other tests where the JVM alone cannot validate
- test: Should contain the tests that run on your local machine.
- Unit tests
7
Fundamentals of Testing →
Configure your test environment
- Consider tradeoffs of running tests on different types of devices
- Real device
- Virtual device (such as the emulator in Android Studio)
- Simulated device (such as Robolectric)
8
Fundamentals of Testing →
Configure your test environment
- Consider whether to use test doubles
Real objects or Test doubles
Test doubles:
- Mock object
- Fake object
- Dummy object
- Test stub
- Test spy
9
Fundamentals of Testing →
Write your tests
- Small tests → unit tests
- Medium tests → integration tests
- Large tests → end-to-end tests
10% large
20% medium
70% small
10
Unit tests
- What’s unit tests?
- Types of unit tests:
- Local tests
- Instrumented tests
- Libraries support
11
Unit tests →
What’s unit tests?
- Unit tests are the fundamental tests
- Easily verify that the logic of individual units
is correct
- Running UTs after every build helps you to
quickly catch and fix.
12
Unit tests →
Types of unit tests
Local tests:
- Run on the local machine only.
- Compiled to run locally on JVM to minimize execution time.
- Depend on your own dependencies → using mock objects to emulate
the dependencies' behavior.
13
Unit tests →
Types of unit tests
Instrumented tests:
- Run on an Android device or emulator.
- Access to instrumentation information (such as the Context)
- Run unit tests that have complex Android dependencies that require a
more robust environment, such as Robolectric.
14
Unit tests →
Libraries support
- JUnit4 or JUnit5
- Mockito (or its Kotlin version mockito-kotlin)
- Mockk
- Robolectric
- AndroidJUnit4
15
Write unit tests for Clean Architecture
- Domain
- Use cases
- DI module
- Data
- Mapper
- Repository
- Remote
- Local
- DI module
- Presentation
- Mapper
- View Model
- DI Module
16
Write unit tests for Clean Architecture
- Domain
- Use cases
- DI module
17
Write unit tests for Clean Architecture → Domain →
Use cases
18
class GetTopUsersUseCase(
private val homeRepository: HomeRepository
) : UseCase<UseCaseParams.Empty, List<UserEntity>>() {
override suspend fun executeInternal(
params: UseCaseParams.Empty
): Either<Failure, List<UserEntity>> {
return homeRepository.getTopUsers()
}
}
Write unit tests for Clean Architecture → Domain →
Use cases Test success
19
@RunWith(JUnit4::class)
class GetTopUsersUseCaseTest {
private val repository = mockk<HomeRepository>()
private val useCase = GetTopUsersUseCase(repository)
@Test
fun executeInternal_success() = runBlocking {
val expected = provideUserEntityList()
coEvery { repository.getTopUsers() } returns Either.Success(expected)
useCase.execute(UseCaseParams.Empty).either(
failAction = { assertTrue(false) },
successAction = { actual -> assertEquals(expected, actual) }
)
}
}
Write unit tests for Clean Architecture → Domain →
Use cases Test error
20
@RunWith(JUnit4::class)
class GetTopUsersUseCaseTest {
private val repository = mockk<HomeRepository>()
private val useCase = GetTopUsersUseCase(repository)
@Test
fun executeInternal_error() = runBlocking {
val expected = provideFailEither()
coEvery { repository.getTopUsers() } returns expected
useCase.execute(UseCaseParams.Empty).either(
failAction = { actual -> assertEquals(actual, expected) },
successAction = { assertTrue(false) }
)
}
}
Write unit tests for Clean Architecture → Domain →
DI modules
21
val createDomainModule = module {
factory { GetTopUsersUseCase(homeRepository = get()) }
factory { GetUserDetailUseCase(detailRepository = get()) }
}
Write unit tests for Clean Architecture → Domain →
DI modules Test
22
@Category(CheckModuleTest::class)
class DomainModuleTest : AutoCloseKoinTest() {
@Test
fun checkModules() = checkModules {
val mockModule = module {
factory { mockk<HomeRepository>() }
factory { mockk<UserDetailRepository>() }
}
modules(createDomainModule, mockModule)
}
}
Write unit tests for Clean Architecture
- Data
- Mapper
- Repository
- Remote
- Local
- DI module
23
Write unit tests for Clean Architecture → Data→
Mapper
24
class UserRemoteEntityMapper : Mapper<UserResponse, UserEntity>() {
override fun map(input: UserResponse): UserEntity = UserEntity(
id = input.id.defaultEmpty(),
login = input.login.defaultEmpty(),
avatarUrl = input.avatarUrl.defaultEmpty(),
name = input.name.defaultEmpty(),
company = input.company.defaultEmpty(),
blog = input.blog.defaultEmpty(),
lastRefreshed = Date()
)
}
Write unit tests for Clean Architecture → Data→
Mapper Test
25
class UserRemoteEntityMapperTest {
private val userRemoteEntityMapper = UserRemoteEntityMapper()
@Test
fun map() {
val userResponse = provideUserResponse()
val actual = userRemoteEntityMapper.map(userResponse)
val expected = provideUserEntity().copy(lastRefreshed = actual.lastRefreshed)
assertEquals(expected, actual)
}
}
Write unit tests for Clean Architecture → Data→
Repository
26
class HomeRepositoryImpl(
private val userDataSource: UserDataSource,
private val userDao: UserDao,
private val remoteExceptionInterceptor: RemoteExceptionInterceptor,
private val userLocalEntityMapper: UserLocalEntityMapper,
private val userResponseLocalMapper: UserResponseLocalMapper
) : HomeRepository {
override suspend fun getTopUsers(): Either<Failure, List<UserEntity>> =
Either.runSuspendWithCatchError(errorInterceptors = listOf(remoteExceptionInterceptor)) {
val dbResult = userLocalEntityMapper.mapList(userDao.getTopUsers())
if (dbResult.isNullOrEmpty()) {
val response = userDataSource.fetchTopUsersAsync()
val userDBOs = userResponseLocalMapper.mapList(response.items)
userDao.insertUses(userDBOs)
val dbAfterInsert = userLocalEntityMapper.mapList(userDao.getTopUsers())
return@runSuspendWithCatchError Either.Success(dbAfterInsert)
} else {
return@runSuspendWithCatchError Either.Success(dbResult)
}
}
}
Write unit tests for Clean Architecture → Data→
Repository Mocking
27
class HomeRepositoryImplTest {
private val userDataSource: UserDataSource = mockk()
private val userDao: UserDao = mockk()
private val remoteInterceptor: RemoteExceptionInterceptor = mockk()
private val userLocalEntityMapper: UserLocalEntityMapper = mockk()
private val userResponseLocalMapper: UserResponseLocalMapper = mockk()
private val detailRepositoryImpl = HomeRepositoryImpl(
userDataSource = userDataSource,
userDao = userDao,
remoteExceptionInterceptor = remoteInterceptor,
userLocalEntityMapper = userLocalEntityMapper,
userResponseLocalMapper = userResponseLocalMapper
)
//...
}
Write unit tests for Clean Architecture → Data→
Repository Test success from remote
28
class HomeRepositoryImplTest {
// ...
@Test
fun getTopUsers_success_fromRemote() = runBlocking {
val userResponseList = provideUserResponseList()
val userDBOList = provideUserDBOList()
val expected = provideUserEntityList()
val userEntity = provideUserEntity()
coEvery { userDao.getTopUsers() } returnsMany listOf(listOf(), userDBOList)
coEvery { userDataSource.fetchTopUsersAsync() } returns userResponseList
coEvery { userDao.insertUses(userDBOList) } returns Unit
every { userLocalEntityMapper.mapList(listOf()) } returns listOf()
every { userResponseLocalMapper.mapList(userResponseList.items) } returns userDBOList
every { userLocalEntityMapper.mapList(userDBOList) } returns expected
detailRepositoryImpl.getTopUsers().either(
failAction = { assertTrue(false) },
successAction = { actual ->
assertEquals(expected, actual)
}
)
}
}
Write unit tests for Clean Architecture → Data→
Repository Test error
29
class HomeRepositoryImplTest {
@Test
fun getTopUsers_error() = runBlocking {
val exception = provideException()
coEvery { userDao.getTopUsers() } returns listOf()
coEvery { userDataSource.fetchTopUsersAsync() } throws exception
every { userLocalEntityMapper.mapList(listOf()) } returns listOf()
every {
remoteInterceptor.handleException(exception)
} returns Failure.UnCatchError(exception)
detailRepositoryImpl.getTopUsers().either(
failAction = { assertTrue(true) },
successAction = { assertTrue(false) }
)
}
}
Write unit tests for Clean Architecture
- Data
- Mapper
- Repository implemented
- Remote
- Local
- DI module
30
Write unit tests for Clean Architecture → Data→ Remote
UserDataSource
31
class UserDataSource(private val userService: UserService) {
suspend fun fetchTopUsersAsync() = userService.fetchTopUsersAsync()
}
Write unit tests for Clean Architecture → Data→ Remote
UserDataSource Test
32
class UserDataSourceTest {
private val userService: UserService = mockk()
private val userDataSource = UserDataSource(userService)
@Test
fun fetchTopUsersAsync() = runBlocking {
val expected = provideUserResponseList()
coEvery { userService.fetchTopUsersAsync() } returns expected
val actual = userDataSource.fetchTopUsersAsync()
Assert.assertEquals(expected, actual)
}
}
Write unit tests for Clean Architecture → Data→ Remote
UserService
33
interface UserService {
@GET("search/users")
suspend fun fetchTopUsersAsync(
@Query("q") query: String = "PhilippeB",
@Query("sort") sort: String = "followers"
): ApiResult<UserResponse>
}
Write unit tests for Clean Architecture → Data→ Remote
UserService Test
34
open class UserServiceTest() {
private lateinit var mockServer: MockWebServer
protected val userDataSource: UserDataSource by inject()
@Before
fun setup() {
mockServer = MockWebServer()
mockServer.start()
startKoin { modules(listOf(createRemoteModule(mockServer.url("/").toString()))) }
}
@After
fun tearDown() {
mockServer.shutdown()
stopKoin()
}
}
Write unit tests for Clean Architecture → Data→ Remote
UserService Test
35
class UserServiceTest() : AutoCloseKoinTest() {
@Test
fun fetchTopUsersAsync_success() {
mockServer.enqueue(
MockResponse()
.setResponseCode(HttpURLConnection.HTTP_OK)
.setBody(getJson("search_users.json"))
)
runBlocking {
val users = userDataSource.fetchTopUsersAsync()
assertEquals(1, users.items.size)
assertEquals("6847959", users.items.first().id)
assertEquals("PhilippeBoisney", users.items.first().login)
assertEquals(
"https://avatars0.githubusercontent.com/u/6847959?v=4",
users.items.first().avatarUrl
)
}
}
}
Write unit tests for Clean Architecture
- Data
- Mapper
- Repository
- Remote
- Local
- DI module
36
Write unit tests for Clean Architecture → Data→ Local
Room DAO
37
@Dao
interface UserDao {
@Insert
suspend fun insertUses(userDBOS: List<UserDBO>)
@Query("SELECT * FROM UserDBO ORDER BY login ASC LIMIT 30")
suspend fun getTopUsers(): List<UserDBO>
}
Write unit tests for Clean Architecture → Data→ Local
Room DAO Test
38
@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class UserDaoTest {
private lateinit var userDao: UserDao
private lateinit var database: MulAppDatabase
private val testDispatcher = TestCoroutineDispatcher()
private val testScope = TestCoroutineScope(testDispatcher)
@Before
fun setup() {
val context = InstrumentationRegistry.getInstrumentation().targetContext
database = Room.inMemoryDatabaseBuilder(context, MulAppDatabase::class.java)
.setTransactionExecutor(testDispatcher.asExecutor())
.setQueryExecutor(testDispatcher.asExecutor())
.build()
userDao = database.userDao()
}
@After
@Throws(IOException::class)
fun clear() {
database.close()
}
}
Write unit tests for Clean Architecture → Data→ Local
Room DAO Test
39
@RunWith(AndroidJUnit4::class)
@ExperimentalCoroutinesApi
class UserDaoTest {
// ...
@Test
fun test_insertUsers() = testScope.runBlockingTest {
val expected = provideUserDBOList(10)
userDao.insertUses(expected)
val actual = userDao.getTopUsers()
Assert.assertEquals(expected, actual)
}
}
Write unit tests for Clean Architecture
- Presentation
- Mapper
- View Model
- DI module
40
Write unit tests for Clean Architecture → Presentation →
ViewModel
41
class HomeViewModel(
private val getTopUsersUseCase: GetTopUsersUseCase,
private val appDispatchers: AppDispatchers
) : BaseViewModel() {
val usersLiveData: MutableLiveData<List<UserEntity>> = MutableLiveData()
val isLoading: MutableLiveData<Boolean> = MutableLiveData()
fun loadUsers() = viewModelScope.launch(appDispatchers.main) {
isLoading.value = true
val getUserResult = getTopUsersUseCase.execute(UseCaseParams.Empty)
isLoading.value = false
getUserResult.either(
failAction = {
usersLiveData.value = null
},
successAction = { userEntities ->
usersLiveData.value = userEntities
}
)
}
}
Write unit tests for Clean Architecture → Presentation →
ViewModel Mocking
42
@ExperimentalCoroutinesApi
class HomViewModelTest {
@Rule
@JvmField
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val getTopUsersUseCase = mockk<GetTopUsersUseCase>()
private val appDispatchers = AppDispatchers(TestCoroutineDispatcher(), TestCoroutineDispatcher())
private val homeViewModel = HomeViewModel(getTopUsersUseCase, appDispatchers)
// ...
}
Write unit tests for Clean Architecture → Presentation →
ViewModel Test
43
@ExperimentalCoroutinesApi
class HomeViewModelTest {
// ...
@Test
fun loadUsers_success() {
val expectedUsers = provideUserEntityList()
val observerUsers: Observer<List<UserEntity>> = mockk(relaxed = true)
val observerLoading: Observer<Boolean> = mockk(relaxed = true)
coEvery { getTopUsersUseCase.execute(UseCaseParams.Empty) } returns Either.Success(expectedUsers)
homeViewModel.usersLiveData.observeForever(observerUsers)
homeViewModel.isLoading.observeForever(observerLoading)
homeViewModel.loadUsers()
verify {
observerUsers.onChanged(expectedUsers)
}
verifySequence {
observerLoading.onChanged(true)
observerLoading.onChanged(false)
}
confirmVerified(observerUsers)
}
}
Write unit tests for Clean Architecture → Presentation → ViewModel Test →
Relaxed
44
class Divider() {
fun divide(p1: Int, p2: Int): Float {
return p1.toFloat() / p2
}
}
class Calculator(val div: Divider) {
fun executeDivide(p1: Int, p2: Int): Float {
return div.divide(p1, p2)
}
}
@Test
fun divTest() {
val mockDiv = mockk<Divider>(relaxed = true)
val cal = Calculator(div = mockDiv)
cal.executeDivide(1, 2)
verify { mockDiv.divide(1, 2) }
}
--------------------------------------------------------
@Test
fun divTest() {
val mockDiv = mockk<Divider>(relaxed = false)
every { mockDiv.divide(1, 2) } returns 0.5f
val cal = Calculator(div = mockDiv)
cal.executeDivide(1, 2)
verify { mockDiv.divide(1, 2) }
}
Test coverage
- Android Studio
- Jacoco
45
Test coverage →
Run code coverage in Android Studio
46
Navigate to the src/test/java folder
→ Right click
→ Select Run ‘Tests in ‘com’’ with Coverage
Test coverage →
Jacoco task
task fullCoverageReport(type: JacocoReport) {
dependsOn 'createDebugCoverageReport'
dependsOn 'testDebugUnitTest'
reports {
xml.enabled = true
html.enabled = true
}
def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*',
'**/*Test*.*', 'android/**/*.*']
def debugTree = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", excludes: fileFilter)
def mainSrc = "${project.projectDir}/src/main/java"
sourceDirectories = files([mainSrc])
classDirectories = files([debugTree])
executionData = fileTree(dir: "$buildDir", includes: [
"jacoco/testDebugUnitTest.exec",
"outputs/code-coverage/connected/*coverage.ec"
])
}
47
Test coverage →
Run a Jacoco task
48
Using
./gradlew fullCoverageReport OR
References
1. Test apps on Android
2. A guide to test pyramid in Android — Part 1
3. GithubBrowserSample on GitHub
4. MVP Clean Architecture in Tiendeo app
5. Unit test pull request from NHN-base source
49
50

More Related Content

What's hot

Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016
Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016
Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016Christian Schneider
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockRobot Media
 
Testing basics for developers
Testing basics for developersTesting basics for developers
Testing basics for developersAnton Udovychenko
 
Android programming -_pushing_the_limits
Android programming -_pushing_the_limitsAndroid programming -_pushing_the_limits
Android programming -_pushing_the_limitsDroidcon Berlin
 
How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?reallavalamp
 
Exploiting Deserialization Vulnerabilities in Java
Exploiting Deserialization Vulnerabilities in JavaExploiting Deserialization Vulnerabilities in Java
Exploiting Deserialization Vulnerabilities in JavaCODE WHITE GmbH
 
Don't Make Android Bad... Again
Don't Make Android Bad... AgainDon't Make Android Bad... Again
Don't Make Android Bad... AgainPedro Vicente
 
JMockit Framework Overview
JMockit Framework OverviewJMockit Framework Overview
JMockit Framework OverviewMario Peshev
 
Java Deserialization Vulnerabilities - The Forgotten Bug Class
Java Deserialization Vulnerabilities - The Forgotten Bug ClassJava Deserialization Vulnerabilities - The Forgotten Bug Class
Java Deserialization Vulnerabilities - The Forgotten Bug ClassCODE WHITE GmbH
 
RelProxy, Easy Class Reload and Scripting with Java
RelProxy, Easy Class Reload and Scripting with JavaRelProxy, Easy Class Reload and Scripting with Java
RelProxy, Easy Class Reload and Scripting with JavaJose María Arranz
 
Bytecode manipulation with Javassist and ASM
Bytecode manipulation with Javassist and ASMBytecode manipulation with Javassist and ASM
Bytecode manipulation with Javassist and ASMashleypuls
 
Spring Framework Petclinic sample application
Spring Framework Petclinic sample applicationSpring Framework Petclinic sample application
Spring Framework Petclinic sample applicationAntoine Rey
 
Android testing
Android testingAndroid testing
Android testingSean Tsai
 
A fresh look at Java Enterprise Application testing with Arquillian
A fresh look at Java Enterprise Application testing with ArquillianA fresh look at Java Enterprise Application testing with Arquillian
A fresh look at Java Enterprise Application testing with ArquillianVineet Reynolds
 

What's hot (20)

Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016
Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016
Surviving the Java Deserialization Apocalypse // OWASP AppSecEU 2016
 
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMockUnit testing in iOS featuring OCUnit, GHUnit & OCMock
Unit testing in iOS featuring OCUnit, GHUnit & OCMock
 
Automation patterns on practice
Automation patterns on practiceAutomation patterns on practice
Automation patterns on practice
 
Testing basics for developers
Testing basics for developersTesting basics for developers
Testing basics for developers
 
Android programming -_pushing_the_limits
Android programming -_pushing_the_limitsAndroid programming -_pushing_the_limits
Android programming -_pushing_the_limits
 
How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?How Does Kubernetes Build OpenAPI Specifications?
How Does Kubernetes Build OpenAPI Specifications?
 
Exploiting Deserialization Vulnerabilities in Java
Exploiting Deserialization Vulnerabilities in JavaExploiting Deserialization Vulnerabilities in Java
Exploiting Deserialization Vulnerabilities in Java
 
Don't Make Android Bad... Again
Don't Make Android Bad... AgainDon't Make Android Bad... Again
Don't Make Android Bad... Again
 
JMockit Framework Overview
JMockit Framework OverviewJMockit Framework Overview
JMockit Framework Overview
 
JUnit 5
JUnit 5JUnit 5
JUnit 5
 
Qunit Java script Un
Qunit Java script UnQunit Java script Un
Qunit Java script Un
 
Java Deserialization Vulnerabilities - The Forgotten Bug Class
Java Deserialization Vulnerabilities - The Forgotten Bug ClassJava Deserialization Vulnerabilities - The Forgotten Bug Class
Java Deserialization Vulnerabilities - The Forgotten Bug Class
 
JDK Power Tools
JDK Power ToolsJDK Power Tools
JDK Power Tools
 
Fixing the Java Serialization Mess
Fixing the Java Serialization Mess Fixing the Java Serialization Mess
Fixing the Java Serialization Mess
 
RelProxy, Easy Class Reload and Scripting with Java
RelProxy, Easy Class Reload and Scripting with JavaRelProxy, Easy Class Reload and Scripting with Java
RelProxy, Easy Class Reload and Scripting with Java
 
Bytecode manipulation with Javassist and ASM
Bytecode manipulation with Javassist and ASMBytecode manipulation with Javassist and ASM
Bytecode manipulation with Javassist and ASM
 
Spring Framework Petclinic sample application
Spring Framework Petclinic sample applicationSpring Framework Petclinic sample application
Spring Framework Petclinic sample application
 
Junit With Eclipse
Junit With EclipseJunit With Eclipse
Junit With Eclipse
 
Android testing
Android testingAndroid testing
Android testing
 
A fresh look at Java Enterprise Application testing with Arquillian
A fresh look at Java Enterprise Application testing with ArquillianA fresh look at Java Enterprise Application testing with Arquillian
A fresh look at Java Enterprise Application testing with Arquillian
 

Similar to Android Unit Test

Real world cross-platform testing
Real world cross-platform testingReal world cross-platform testing
Real world cross-platform testingPeter Edwards
 
Test Automation for NoSQL Databases
Test Automation for NoSQL DatabasesTest Automation for NoSQL Databases
Test Automation for NoSQL DatabasesTobias Trelle
 
Integration tests: use the containers, Luke!
Integration tests: use the containers, Luke!Integration tests: use the containers, Luke!
Integration tests: use the containers, Luke!Roberto Franchini
 
JUnit5 and TestContainers
JUnit5 and TestContainersJUnit5 and TestContainers
JUnit5 and TestContainersSunghyouk Bae
 
Guide to the jungle of testing frameworks
Guide to the jungle of testing frameworksGuide to the jungle of testing frameworks
Guide to the jungle of testing frameworksTomáš Kypta
 
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014FalafelSoftware
 
Breaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingBreaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingSteven Smith
 
Oh so you test? - A guide to testing on Android from Unit to Mutation
Oh so you test? - A guide to testing on Android from Unit to MutationOh so you test? - A guide to testing on Android from Unit to Mutation
Oh so you test? - A guide to testing on Android from Unit to MutationPaul Blundell
 
An introduction to Test Driven Development on MapReduce
An introduction to Test Driven Development on MapReduceAn introduction to Test Driven Development on MapReduce
An introduction to Test Driven Development on MapReduceAnanth PackkilDurai
 
PHP Unit Testing in Yii
PHP Unit Testing in YiiPHP Unit Testing in Yii
PHP Unit Testing in YiiIlPeach
 
谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testability谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testabilitydrewz lin
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testingpleeps
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Mobile Developer Day
 
BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...
BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...
BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...Mark A
 
Unit Testing on Android - Droidcon Berlin 2015
Unit Testing on Android - Droidcon Berlin 2015Unit Testing on Android - Droidcon Berlin 2015
Unit Testing on Android - Droidcon Berlin 2015Buşra Deniz, CSM
 
Testing your application on Google App Engine
Testing your application on Google App EngineTesting your application on Google App Engine
Testing your application on Google App EngineInphina Technologies
 
Testing Your Application On Google App Engine
Testing Your Application On Google App EngineTesting Your Application On Google App Engine
Testing Your Application On Google App EngineIndicThreads
 
Guide to the jungle of testing frameworks
Guide to the jungle of testing frameworksGuide to the jungle of testing frameworks
Guide to the jungle of testing frameworksTomáš Kypta
 

Similar to Android Unit Test (20)

Real world cross-platform testing
Real world cross-platform testingReal world cross-platform testing
Real world cross-platform testing
 
Test Automation for NoSQL Databases
Test Automation for NoSQL DatabasesTest Automation for NoSQL Databases
Test Automation for NoSQL Databases
 
Integration tests: use the containers, Luke!
Integration tests: use the containers, Luke!Integration tests: use the containers, Luke!
Integration tests: use the containers, Luke!
 
JUnit5 and TestContainers
JUnit5 and TestContainersJUnit5 and TestContainers
JUnit5 and TestContainers
 
Guide to the jungle of testing frameworks
Guide to the jungle of testing frameworksGuide to the jungle of testing frameworks
Guide to the jungle of testing frameworks
 
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
Breaking Dependencies To Allow Unit Testing - Steve Smith | FalafelCON 2014
 
Breaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit TestingBreaking Dependencies to Allow Unit Testing
Breaking Dependencies to Allow Unit Testing
 
Oh so you test? - A guide to testing on Android from Unit to Mutation
Oh so you test? - A guide to testing on Android from Unit to MutationOh so you test? - A guide to testing on Android from Unit to Mutation
Oh so you test? - A guide to testing on Android from Unit to Mutation
 
An introduction to Test Driven Development on MapReduce
An introduction to Test Driven Development on MapReduceAn introduction to Test Driven Development on MapReduce
An introduction to Test Driven Development on MapReduce
 
PHP Unit Testing in Yii
PHP Unit Testing in YiiPHP Unit Testing in Yii
PHP Unit Testing in Yii
 
谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testability谷歌 Scott-lessons learned in testability
谷歌 Scott-lessons learned in testability
 
Grails unit testing
Grails unit testingGrails unit testing
Grails unit testing
 
Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)Automated integration tests for ajax applications (с. карпушин, auriga)
Automated integration tests for ajax applications (с. карпушин, auriga)
 
BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...
BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...
BizSpark SF Lightning Talk: "Automated Testing (Unit, Integration and Systems...
 
Unit Testing on Android - Droidcon Berlin 2015
Unit Testing on Android - Droidcon Berlin 2015Unit Testing on Android - Droidcon Berlin 2015
Unit Testing on Android - Droidcon Berlin 2015
 
Testing your application on Google App Engine
Testing your application on Google App EngineTesting your application on Google App Engine
Testing your application on Google App Engine
 
Testing Your Application On Google App Engine
Testing Your Application On Google App EngineTesting Your Application On Google App Engine
Testing Your Application On Google App Engine
 
Guide to the jungle of testing frameworks
Guide to the jungle of testing frameworksGuide to the jungle of testing frameworks
Guide to the jungle of testing frameworks
 
Agile Swift
Agile SwiftAgile Swift
Agile Swift
 
Unit testing
Unit testingUnit testing
Unit testing
 

Android Unit Test

  • 1. Android Unit Test Bui Huu Phuoc HCMC - Vietnam, 24/07/2020 1
  • 2. Table of contents - What’s Android testing? - Fundamentals of Testing - Unit tests - Write unit tests for Clean Architecture - Test coverage - References 2
  • 3. What’s Android testing? - It’s a part of the app development process. - Verify your app's correctness, functional behavior, and usability before you release it publicly. - Rapid feedback on failures. - Early failure detection in the development cycle. - Safer code refactoring, letting you optimize code without worrying about regressions. - Stable development velocity, helping you minimize technical debt. 3
  • 4. Fundamentals of Testing - Organize your code for testing - Configure your test environment - Write your tests 4
  • 5. Fundamentals of Testing → Organize your code for testing - Create and test code iteratively 5
  • 6. Fundamentals of Testing → Organize your code for testing - View your app as a series of modules 6
  • 7. Fundamentals of Testing → Configure your test environment - Organize test directories based on execution environment - androidTest: Directory should contain the tests that run on real or virtual devices. - Integration tests - End-to-end tests - Other tests where the JVM alone cannot validate - test: Should contain the tests that run on your local machine. - Unit tests 7
  • 8. Fundamentals of Testing → Configure your test environment - Consider tradeoffs of running tests on different types of devices - Real device - Virtual device (such as the emulator in Android Studio) - Simulated device (such as Robolectric) 8
  • 9. Fundamentals of Testing → Configure your test environment - Consider whether to use test doubles Real objects or Test doubles Test doubles: - Mock object - Fake object - Dummy object - Test stub - Test spy 9
  • 10. Fundamentals of Testing → Write your tests - Small tests → unit tests - Medium tests → integration tests - Large tests → end-to-end tests 10% large 20% medium 70% small 10
  • 11. Unit tests - What’s unit tests? - Types of unit tests: - Local tests - Instrumented tests - Libraries support 11
  • 12. Unit tests → What’s unit tests? - Unit tests are the fundamental tests - Easily verify that the logic of individual units is correct - Running UTs after every build helps you to quickly catch and fix. 12
  • 13. Unit tests → Types of unit tests Local tests: - Run on the local machine only. - Compiled to run locally on JVM to minimize execution time. - Depend on your own dependencies → using mock objects to emulate the dependencies' behavior. 13
  • 14. Unit tests → Types of unit tests Instrumented tests: - Run on an Android device or emulator. - Access to instrumentation information (such as the Context) - Run unit tests that have complex Android dependencies that require a more robust environment, such as Robolectric. 14
  • 15. Unit tests → Libraries support - JUnit4 or JUnit5 - Mockito (or its Kotlin version mockito-kotlin) - Mockk - Robolectric - AndroidJUnit4 15
  • 16. Write unit tests for Clean Architecture - Domain - Use cases - DI module - Data - Mapper - Repository - Remote - Local - DI module - Presentation - Mapper - View Model - DI Module 16
  • 17. Write unit tests for Clean Architecture - Domain - Use cases - DI module 17
  • 18. Write unit tests for Clean Architecture → Domain → Use cases 18 class GetTopUsersUseCase( private val homeRepository: HomeRepository ) : UseCase<UseCaseParams.Empty, List<UserEntity>>() { override suspend fun executeInternal( params: UseCaseParams.Empty ): Either<Failure, List<UserEntity>> { return homeRepository.getTopUsers() } }
  • 19. Write unit tests for Clean Architecture → Domain → Use cases Test success 19 @RunWith(JUnit4::class) class GetTopUsersUseCaseTest { private val repository = mockk<HomeRepository>() private val useCase = GetTopUsersUseCase(repository) @Test fun executeInternal_success() = runBlocking { val expected = provideUserEntityList() coEvery { repository.getTopUsers() } returns Either.Success(expected) useCase.execute(UseCaseParams.Empty).either( failAction = { assertTrue(false) }, successAction = { actual -> assertEquals(expected, actual) } ) } }
  • 20. Write unit tests for Clean Architecture → Domain → Use cases Test error 20 @RunWith(JUnit4::class) class GetTopUsersUseCaseTest { private val repository = mockk<HomeRepository>() private val useCase = GetTopUsersUseCase(repository) @Test fun executeInternal_error() = runBlocking { val expected = provideFailEither() coEvery { repository.getTopUsers() } returns expected useCase.execute(UseCaseParams.Empty).either( failAction = { actual -> assertEquals(actual, expected) }, successAction = { assertTrue(false) } ) } }
  • 21. Write unit tests for Clean Architecture → Domain → DI modules 21 val createDomainModule = module { factory { GetTopUsersUseCase(homeRepository = get()) } factory { GetUserDetailUseCase(detailRepository = get()) } }
  • 22. Write unit tests for Clean Architecture → Domain → DI modules Test 22 @Category(CheckModuleTest::class) class DomainModuleTest : AutoCloseKoinTest() { @Test fun checkModules() = checkModules { val mockModule = module { factory { mockk<HomeRepository>() } factory { mockk<UserDetailRepository>() } } modules(createDomainModule, mockModule) } }
  • 23. Write unit tests for Clean Architecture - Data - Mapper - Repository - Remote - Local - DI module 23
  • 24. Write unit tests for Clean Architecture → Data→ Mapper 24 class UserRemoteEntityMapper : Mapper<UserResponse, UserEntity>() { override fun map(input: UserResponse): UserEntity = UserEntity( id = input.id.defaultEmpty(), login = input.login.defaultEmpty(), avatarUrl = input.avatarUrl.defaultEmpty(), name = input.name.defaultEmpty(), company = input.company.defaultEmpty(), blog = input.blog.defaultEmpty(), lastRefreshed = Date() ) }
  • 25. Write unit tests for Clean Architecture → Data→ Mapper Test 25 class UserRemoteEntityMapperTest { private val userRemoteEntityMapper = UserRemoteEntityMapper() @Test fun map() { val userResponse = provideUserResponse() val actual = userRemoteEntityMapper.map(userResponse) val expected = provideUserEntity().copy(lastRefreshed = actual.lastRefreshed) assertEquals(expected, actual) } }
  • 26. Write unit tests for Clean Architecture → Data→ Repository 26 class HomeRepositoryImpl( private val userDataSource: UserDataSource, private val userDao: UserDao, private val remoteExceptionInterceptor: RemoteExceptionInterceptor, private val userLocalEntityMapper: UserLocalEntityMapper, private val userResponseLocalMapper: UserResponseLocalMapper ) : HomeRepository { override suspend fun getTopUsers(): Either<Failure, List<UserEntity>> = Either.runSuspendWithCatchError(errorInterceptors = listOf(remoteExceptionInterceptor)) { val dbResult = userLocalEntityMapper.mapList(userDao.getTopUsers()) if (dbResult.isNullOrEmpty()) { val response = userDataSource.fetchTopUsersAsync() val userDBOs = userResponseLocalMapper.mapList(response.items) userDao.insertUses(userDBOs) val dbAfterInsert = userLocalEntityMapper.mapList(userDao.getTopUsers()) return@runSuspendWithCatchError Either.Success(dbAfterInsert) } else { return@runSuspendWithCatchError Either.Success(dbResult) } } }
  • 27. Write unit tests for Clean Architecture → Data→ Repository Mocking 27 class HomeRepositoryImplTest { private val userDataSource: UserDataSource = mockk() private val userDao: UserDao = mockk() private val remoteInterceptor: RemoteExceptionInterceptor = mockk() private val userLocalEntityMapper: UserLocalEntityMapper = mockk() private val userResponseLocalMapper: UserResponseLocalMapper = mockk() private val detailRepositoryImpl = HomeRepositoryImpl( userDataSource = userDataSource, userDao = userDao, remoteExceptionInterceptor = remoteInterceptor, userLocalEntityMapper = userLocalEntityMapper, userResponseLocalMapper = userResponseLocalMapper ) //... }
  • 28. Write unit tests for Clean Architecture → Data→ Repository Test success from remote 28 class HomeRepositoryImplTest { // ... @Test fun getTopUsers_success_fromRemote() = runBlocking { val userResponseList = provideUserResponseList() val userDBOList = provideUserDBOList() val expected = provideUserEntityList() val userEntity = provideUserEntity() coEvery { userDao.getTopUsers() } returnsMany listOf(listOf(), userDBOList) coEvery { userDataSource.fetchTopUsersAsync() } returns userResponseList coEvery { userDao.insertUses(userDBOList) } returns Unit every { userLocalEntityMapper.mapList(listOf()) } returns listOf() every { userResponseLocalMapper.mapList(userResponseList.items) } returns userDBOList every { userLocalEntityMapper.mapList(userDBOList) } returns expected detailRepositoryImpl.getTopUsers().either( failAction = { assertTrue(false) }, successAction = { actual -> assertEquals(expected, actual) } ) } }
  • 29. Write unit tests for Clean Architecture → Data→ Repository Test error 29 class HomeRepositoryImplTest { @Test fun getTopUsers_error() = runBlocking { val exception = provideException() coEvery { userDao.getTopUsers() } returns listOf() coEvery { userDataSource.fetchTopUsersAsync() } throws exception every { userLocalEntityMapper.mapList(listOf()) } returns listOf() every { remoteInterceptor.handleException(exception) } returns Failure.UnCatchError(exception) detailRepositoryImpl.getTopUsers().either( failAction = { assertTrue(true) }, successAction = { assertTrue(false) } ) } }
  • 30. Write unit tests for Clean Architecture - Data - Mapper - Repository implemented - Remote - Local - DI module 30
  • 31. Write unit tests for Clean Architecture → Data→ Remote UserDataSource 31 class UserDataSource(private val userService: UserService) { suspend fun fetchTopUsersAsync() = userService.fetchTopUsersAsync() }
  • 32. Write unit tests for Clean Architecture → Data→ Remote UserDataSource Test 32 class UserDataSourceTest { private val userService: UserService = mockk() private val userDataSource = UserDataSource(userService) @Test fun fetchTopUsersAsync() = runBlocking { val expected = provideUserResponseList() coEvery { userService.fetchTopUsersAsync() } returns expected val actual = userDataSource.fetchTopUsersAsync() Assert.assertEquals(expected, actual) } }
  • 33. Write unit tests for Clean Architecture → Data→ Remote UserService 33 interface UserService { @GET("search/users") suspend fun fetchTopUsersAsync( @Query("q") query: String = "PhilippeB", @Query("sort") sort: String = "followers" ): ApiResult<UserResponse> }
  • 34. Write unit tests for Clean Architecture → Data→ Remote UserService Test 34 open class UserServiceTest() { private lateinit var mockServer: MockWebServer protected val userDataSource: UserDataSource by inject() @Before fun setup() { mockServer = MockWebServer() mockServer.start() startKoin { modules(listOf(createRemoteModule(mockServer.url("/").toString()))) } } @After fun tearDown() { mockServer.shutdown() stopKoin() } }
  • 35. Write unit tests for Clean Architecture → Data→ Remote UserService Test 35 class UserServiceTest() : AutoCloseKoinTest() { @Test fun fetchTopUsersAsync_success() { mockServer.enqueue( MockResponse() .setResponseCode(HttpURLConnection.HTTP_OK) .setBody(getJson("search_users.json")) ) runBlocking { val users = userDataSource.fetchTopUsersAsync() assertEquals(1, users.items.size) assertEquals("6847959", users.items.first().id) assertEquals("PhilippeBoisney", users.items.first().login) assertEquals( "https://avatars0.githubusercontent.com/u/6847959?v=4", users.items.first().avatarUrl ) } } }
  • 36. Write unit tests for Clean Architecture - Data - Mapper - Repository - Remote - Local - DI module 36
  • 37. Write unit tests for Clean Architecture → Data→ Local Room DAO 37 @Dao interface UserDao { @Insert suspend fun insertUses(userDBOS: List<UserDBO>) @Query("SELECT * FROM UserDBO ORDER BY login ASC LIMIT 30") suspend fun getTopUsers(): List<UserDBO> }
  • 38. Write unit tests for Clean Architecture → Data→ Local Room DAO Test 38 @RunWith(AndroidJUnit4::class) @ExperimentalCoroutinesApi class UserDaoTest { private lateinit var userDao: UserDao private lateinit var database: MulAppDatabase private val testDispatcher = TestCoroutineDispatcher() private val testScope = TestCoroutineScope(testDispatcher) @Before fun setup() { val context = InstrumentationRegistry.getInstrumentation().targetContext database = Room.inMemoryDatabaseBuilder(context, MulAppDatabase::class.java) .setTransactionExecutor(testDispatcher.asExecutor()) .setQueryExecutor(testDispatcher.asExecutor()) .build() userDao = database.userDao() } @After @Throws(IOException::class) fun clear() { database.close() } }
  • 39. Write unit tests for Clean Architecture → Data→ Local Room DAO Test 39 @RunWith(AndroidJUnit4::class) @ExperimentalCoroutinesApi class UserDaoTest { // ... @Test fun test_insertUsers() = testScope.runBlockingTest { val expected = provideUserDBOList(10) userDao.insertUses(expected) val actual = userDao.getTopUsers() Assert.assertEquals(expected, actual) } }
  • 40. Write unit tests for Clean Architecture - Presentation - Mapper - View Model - DI module 40
  • 41. Write unit tests for Clean Architecture → Presentation → ViewModel 41 class HomeViewModel( private val getTopUsersUseCase: GetTopUsersUseCase, private val appDispatchers: AppDispatchers ) : BaseViewModel() { val usersLiveData: MutableLiveData<List<UserEntity>> = MutableLiveData() val isLoading: MutableLiveData<Boolean> = MutableLiveData() fun loadUsers() = viewModelScope.launch(appDispatchers.main) { isLoading.value = true val getUserResult = getTopUsersUseCase.execute(UseCaseParams.Empty) isLoading.value = false getUserResult.either( failAction = { usersLiveData.value = null }, successAction = { userEntities -> usersLiveData.value = userEntities } ) } }
  • 42. Write unit tests for Clean Architecture → Presentation → ViewModel Mocking 42 @ExperimentalCoroutinesApi class HomViewModelTest { @Rule @JvmField val instantTaskExecutorRule = InstantTaskExecutorRule() private val getTopUsersUseCase = mockk<GetTopUsersUseCase>() private val appDispatchers = AppDispatchers(TestCoroutineDispatcher(), TestCoroutineDispatcher()) private val homeViewModel = HomeViewModel(getTopUsersUseCase, appDispatchers) // ... }
  • 43. Write unit tests for Clean Architecture → Presentation → ViewModel Test 43 @ExperimentalCoroutinesApi class HomeViewModelTest { // ... @Test fun loadUsers_success() { val expectedUsers = provideUserEntityList() val observerUsers: Observer<List<UserEntity>> = mockk(relaxed = true) val observerLoading: Observer<Boolean> = mockk(relaxed = true) coEvery { getTopUsersUseCase.execute(UseCaseParams.Empty) } returns Either.Success(expectedUsers) homeViewModel.usersLiveData.observeForever(observerUsers) homeViewModel.isLoading.observeForever(observerLoading) homeViewModel.loadUsers() verify { observerUsers.onChanged(expectedUsers) } verifySequence { observerLoading.onChanged(true) observerLoading.onChanged(false) } confirmVerified(observerUsers) } }
  • 44. Write unit tests for Clean Architecture → Presentation → ViewModel Test → Relaxed 44 class Divider() { fun divide(p1: Int, p2: Int): Float { return p1.toFloat() / p2 } } class Calculator(val div: Divider) { fun executeDivide(p1: Int, p2: Int): Float { return div.divide(p1, p2) } } @Test fun divTest() { val mockDiv = mockk<Divider>(relaxed = true) val cal = Calculator(div = mockDiv) cal.executeDivide(1, 2) verify { mockDiv.divide(1, 2) } } -------------------------------------------------------- @Test fun divTest() { val mockDiv = mockk<Divider>(relaxed = false) every { mockDiv.divide(1, 2) } returns 0.5f val cal = Calculator(div = mockDiv) cal.executeDivide(1, 2) verify { mockDiv.divide(1, 2) } }
  • 45. Test coverage - Android Studio - Jacoco 45
  • 46. Test coverage → Run code coverage in Android Studio 46 Navigate to the src/test/java folder → Right click → Select Run ‘Tests in ‘com’’ with Coverage
  • 47. Test coverage → Jacoco task task fullCoverageReport(type: JacocoReport) { dependsOn 'createDebugCoverageReport' dependsOn 'testDebugUnitTest' reports { xml.enabled = true html.enabled = true } def fileFilter = ['**/R.class', '**/R$*.class', '**/BuildConfig.*', '**/Manifest*.*', '**/*Test*.*', 'android/**/*.*'] def debugTree = fileTree(dir: "$buildDir/tmp/kotlin-classes/debug", excludes: fileFilter) def mainSrc = "${project.projectDir}/src/main/java" sourceDirectories = files([mainSrc]) classDirectories = files([debugTree]) executionData = fileTree(dir: "$buildDir", includes: [ "jacoco/testDebugUnitTest.exec", "outputs/code-coverage/connected/*coverage.ec" ]) } 47
  • 48. Test coverage → Run a Jacoco task 48 Using ./gradlew fullCoverageReport OR
  • 49. References 1. Test apps on Android 2. A guide to test pyramid in Android — Part 1 3. GithubBrowserSample on GitHub 4. MVP Clean Architecture in Tiendeo app 5. Unit test pull request from NHN-base source 49
  • 50. 50

Editor's Notes

  1. @Test fun divTest() { val mockDiv = mockk<Div>(relaxed = true) // every { mockDiv.divide(1, 2) } returns 0.5f val cal = Calculator(div = mockDiv) cal.executeDivide(1, 2) verify { mockDiv.divide(1, 2) } } class Div() { fun divide(p1: Int, p2: Int) = p1.toFloat()/p2 } class Calculator(val div: Div) { fun executeDivide(p1: Int, p2: Int) = div.divide(p1, p2) }