-
Notifications
You must be signed in to change notification settings - Fork 0
Testing library β mockk
Devrath edited this page Oct 24, 2023
·
14 revisions
- So we know when we have a class used in another class, We can make a
fake
version of that class and pass the fake implementation where it is being used to avoid errors in the actual implementation. - There is another way where we can mock it.
- Basically mocking involves using a
mirror version
of theactual class implementation
and once it is mirrored we can make the methods of that particular class behave as we want so that when theimplementation
of the class is used in another class they return or respond in a particular way.
- Consider the code
mockk()
and compared to anothermockk(relaxed = true)
- When
relaxed=true
is used the framework will give random values likeempty
,false
, etc .. for the functions that contain return types otherwise the return types are not provided, and the mocked instance bay gives an error while running the test(No answer found
). - Also we use this when we need the instance and do not bother with specific return types so random return types are fine.
- When normal functions are to be invoked we used
every { ... }
- When sus[end functions are involved , We use
coEvery{ ... }
- Sometimes we mock an implementation and via the mock, we access a method of the instance.
- But sometimes there might be an implementation mentioned inside the function and it can't be accessed via the constructor.
- In such a scenario, We need to use
mockkConstructor
ProductApi.kt
interface ProductApi {
@POST("/purchase")
suspend fun purchaseProducts(@Body products: ProductsDto)
@POST("/cancelPurchase/{id}")
suspend fun cancelPurchase(@Path("id") purchaseId: String)
}
ProductRepositoryImpl.kt
class ProductRepositoryImpl(
private val productApi: ProductApi,
private val analyticsLogger: AnalyticsLogger
): ProductRepository {
override suspend fun purchaseProducts(products: List<Product>): Result<Unit> {
return try {
// Observe that we cannot pass the custom Product in constructor since the functionality does not provide it
val custProduct = CustomProduct(id = 1, name = "Power Ranger", price = 10.9)
println(custProduct.name)
productApi.purchaseProducts(
products = ProductsDto(products)
)
Result.success(Unit)
} catch (e: HttpException) {
analyticsLogger.logEvent(
"http_error",
LogParam("code", e.code()),
LogParam("message", e.message()),
)
Result.failure(e)
} catch(e: IOException) {
analyticsLogger.logEvent(
"io_error",
LogParam("message", e.message.toString())
)
Result.failure(e)
} catch (e: Exception) {
if(e is CancellationException) throw e
Result.failure(e)
}
}
override suspend fun cancelPurchase(purchaseId: String): Result<Unit> {
TODO("Not yet implemented")
}
}
class ProductRepositoryImplTest {
// Define the SUT: System under test :-> In our case it is repository
private lateinit var sut : ProductRepositoryImpl
private lateinit var api: ProductApi
private lateinit var logger: AnalyticsLogger
@BeforeEach
fun setup(){
api = mockk()
logger = mockk(relaxed = true)
sut = ProductRepositoryImpl(productApi = api, analyticsLogger = logger)
}
@Test
fun `When there is Response Error for any input , Exception is logged`() = runBlocking {
// Call the API and make it to generate exception mentioned whenever the api is used with any input
coEvery {
api.purchaseProducts(any())
} throws mockk<HttpException>(){
// <---- This describes how this Http Mock should behave ---->
// On the exception, The code() block should return 404 error
every { code() } returns 404
// On the exception, THe message() block should return
every { message() } returns "Test Message"
}
// Now the result must return specific error based on what we assert
val result = sut.purchaseProducts(listOf())
// Check if the failure is true
assertThat(result.isFailure).isTrue()
// Check if the logger event was triggered when error had occured
verify {
logger.logEvent(
"http_error",
LogParam("code", 404),
LogParam("message", "Test Message")
)
}
}
@Test
fun `When there is Response Error for particular input , Exception is logged`() = runBlocking{
// THe api will throw the exception mentioned when its being called with specific input
coEvery {
api.purchaseProducts(
ProductsDto(
listOf(
Product(id = 1, name = "Hello", 5.0)
)
)
)
} throws mockk<HttpException>(){
// <---- This describes how this Http Mock should behave ---->
// On the exception, The code() block should return 404 error
every { code() } returns 404
// On the exception, THe message() block should return
every { message() } returns "Test Message"
}
// Now the result must return specific error based on what we assert
val result = sut.purchaseProducts(listOf(
Product(id = 1, name = "Hello", 5.0)
))
// Check if the failure is true
assertThat(result.isFailure).isTrue()
// Check if the logger event was triggered when error had occured
verify {
logger.logEvent(
"http_error",
LogParam("code", 404),
LogParam("message", "Test Message")
)
}
}
@Test
fun `Mock object that you don't have access to , Exception is logged`() = runBlocking {
// Call the API and make it to generate exception mentioned whenever the api is used with any input
coEvery {
api.purchaseProducts(any())
} throws mockk<HttpException>(){
// <---- This describes how this Http Mock should behave ---->
// On the exception, The code() block should return 404 error
every { code() } returns 404
// On the exception, THe message() block should return
every { message() } returns "Test Message"
}
// Observe the output instead of `power rangers` the output `thunder cats` is printed
mockkConstructor(CustomProduct::class)
every { anyConstructed<CustomProduct>().name } returns "Thunder cats"
// Now the result must return specific error based on what we assert
val result = sut.purchaseProducts(listOf())
// Check if the failure is true
assertThat(result.isFailure).isTrue()
// Check if the logger event was triggered when error had occured
verify {
logger.logEvent(
"http_error",
LogParam("code", 404),
LogParam("message", "Test Message")
)
}
}
}