The Journey of Unit Testing in Android Development
Jakarta, Indonesia
admokonugroho.com
Introduction
Jakarta, Indonesia
Detail Transaction
Rp 110.705
Rp 11.070
Rp 99.635
Rp 0
Rp 0
Rp 99.635
Rp 99.635
Fare x 10%
Rp 11.070,50
Rp 99.634,50
Rp 99.634,50
The Challenge
Impact on User Experience
Lessons Learned
“A test is manual or automatic procedure used to evaluate if the System Under Test behave correctly”
The Test Pyramid
Jakarta, Indonesia
THE TEST PYRAMID
UI TEST
INTEGRATION TEST
UNIT TEST
Slower
Faster
More Integration
More Isolation
UI Test
Integration Test
Unit Test
Clean Architecture
domain
data
presenters
UI
App
Repositories
Database
datasource
Network
datasource
Unit Test - Detail Transaction
class DetailTransaction(
val fare : Double,
val discount: Double,
val maxDiscount: Double
) {
fun calculateDiscount() : Int {
return (fare * discount).roundToInt()
}
}
Unit Test - Detail Transaction
@Test
fun whenCalculateDiscount_shouldCalculateAndRoundUpCorrectly() {
// given
val sut = DetailTransaction(
fare = 12145.2,
discount = 0.1
)
// when
val result = sut.calculateDiscount()
// then
Assertions.assertEquals(1215, result)
}
Unit Test - Detail Transaction
@Test
fun whenCalculateDiscount_noDiscount_shouldCalculateAndRoundDownCorrectly() {
// given
val sut = DetailTransaction(
fare = 12142.2,
discount = 0.0
)
// when
val result = sut.calculateDiscount()
// then
Assertions.assertEquals(12142, result)
}
Caption
Unit Test - Detail Transaction
class DetailTransaction(
val fare : Double,
val discount: Double,
val maxDiscount: Double
) {
fun calculateDiscount() : Int {
return (fare * discount).roundToInt()
}
}
class DetailTransaction(
val fare : Double,
val discount: Double,
val maxDiscount: Double? = null
) {
fun calculateDiscount() : Int {
val result = if (discount > 0) {
(fare * discount)
} else fare
return result.roundToInt()
}
}
What if the fare Rp (any) and discount 20% and max discount 15.000?
Refactor
Update Your Code
Pick a requirement
Write a test to meet
the requirement
Run Test
Test
Succeed?
Modify
/ Refactor Code
No
Yes
Mock
Jakarta, Indonesia
Mockk.io & Junit5
Mock - Detail Transaction
class TransactionRepository @Inject constructor(
val factory: TransactionEntityDataFactory
) {
fun getDetailTransaction(
orderId: String
) : Single<DetailTransaction> {
return factory.createData(Source.NETWORK)
.getDetailTransaction(orderId)
}
}
Mock - Detail Transaction
class TransactionRepositoryTest {
private val factory =
mockk<TransactionEntityDataFactory>()
private val network =
mockk<TransactionEntityData>()
private val local =
mockk<TransactionEntityData>()
private val repository =
TransactionRepository(factory)
@BeforeEach
fun setup() {
clearMocks(factory, local, network)
every {
factory.createData(Source.LOCAL)
} returns local
every {
factory.createData(Source.NETWORK)
} returns network
}
Mock - Detail Transaction
@Test
fun whenGetDetailTransaction_shouldReturnData() {
//Given
val orderId = "order_id_00"
val result = DetailTransaction(
fare = 20000.0,
discount = 0.0
)
every {
network.getDetailTransaction(orderId)
} returns Single.just(
result
)
//When
repository.getDetailTransaction(orderId)
.test()
.apply {
this.assertValueAt(0, result)
}
.dispose()
//Then
verify {
factory.createData(Source.NETWORK)
network.getDetailTransaction(orderId)
}
verify {
local wasNot called
}
}
Continuous Integration
Jakarta, Indonesia
Pull Request
developer
UI / Integration / Unit Test
auto
Pass Quality Gate?
no
auto
Continuous Integration
Merge
Continuous Delivery
Deploy To Test
Acceptance Testing
Continuous Deployment
Deploy / Promote to Production
Conclusion
Jakarta, Indonesia
Conclusion
Reference