
Hey again! Now that you’re comfortable with the basics of MockK, let’s level up and explore some advanced features. We’ll cover things like:
- Mocking static methods, objects, and final classes.
- Using
mockkStatic
,mockkObject
, andmockkClass
. - Advanced constructs like
slot
andcapture
to inspect method arguments.
This is where MockK really shines compared to other libraries like Mockito. Ready? Let’s dive in!
Mocking Static Methods
Sometimes, you need to mock static methods — those pesky functions tied directly to a class, not an instance. In MockK, you can use mockkStatic
to achieve this.
Example of mockkStatic
Let’s say you have a static method in a utility class:
object Utils { fun getCurrentTime(): Long = System.currentTimeMillis() }
In a test, you don’t want to rely on the real system time. Instead, you can mock getCurrentTime()
to return a fixed value:
import io.mockk.every import io.mockk.mockkStatic import org.junit.Assert.assertEquals import org.junit.Test class UtilsTest { @Test fun `test getCurrentTime returns mocked value`() { // Mock the static method mockkStatic(Utils::class) every { Utils.getCurrentTime() } returns 1234567890L // Call the method val result = Utils.getCurrentTime() // Verify the result assertEquals(1234567890L, result) } }
Key Points
- Remember to call
mockkStatic() before stubbing the static method.
- You can reset the mock with
unmockkStatic(Utils::class)
if needed.
Mocking Objects
Sometimes, you need to mock a singleton object in your code. MockK makes this easy with mockkObject
.
Example of mockkObject
Let’s say you have a singleton object:
object ConfigManager { fun getApiUrl(): String = "https://real-api.com" }
To mock this object during testing:
import io.mockk.every import io.mockk.mockkObject import org.junit.Assert.assertEquals import org.junit.Test class ConfigManagerTest { @Test fun `test getApiUrl returns mocked value`() { // Mock the singleton object mockkObject(ConfigManager) every { ConfigManager.getApiUrl() } returns "https://mock-api.com" // Call the method val result = ConfigManager.getApiUrl() // Verify the result assertEquals("https://mock-api.com", result) } }
Key Points
- Use
mockkObject
for mocking singleton objects. - Reset the mock with
unmockkObject(ConfigManager)
if needed.
Mocking Final Classes
In Kotlin, all classes are final
by default, meaning you can’t subclass them. Some mocking libraries struggle with this, but MockK handles it easily with mockkClass
.
Example of mockkClass
Let’s say you have a final class:
class UserRepository { fun getUser(id: Int): String = "Real User" }
To mock this final class in your tests:
import io.mockk.every import io.mockk.mockkClass import org.junit.Assert.assertEquals import org.junit.Test class UserRepositoryTest { @Test fun `test getUser returns mocked value`() { // Create a mock of the final class val mockUserRepository = mockkClass(UserRepository::class) every { mockUserRepository.getUser(1) } returns "Mocked User" // Call the method val result = mockUserRepository.getUser(1) // Verify the result assertEquals("Mocked User", result) } }
Key Points
mockkClass
works with final classes, making MockK perfect for Kotlin.- Reset the mock with
unmockkAll()
if needed.
Advanced Constructs: slot
and capture
Sometimes, you need to capture arguments passed to a mocked function and inspect them later. MockK provides slot
and capture
for this purpose.
What is a slot
?
A slot
is a placeholder where you can store an argument passed to a mocked function.
Example of Using slot
Let’s say you have a function that sends a user ID to an API:
class ApiService { fun sendUserId(userId: Int) { // Sends the user ID to an API } }
You want to verify that the correct user ID is sent. Here’s how you can capture the argument:
import io.mockk.CapturingSlot import io.mockk.every import io.mockk.just import io.mockk.runs import io.mockk.slot import io.mockk.verify import org.junit.Assert.assertEquals import org.junit.Test class ApiServiceTest { @Test fun `test sendUserId captures the correct user ID`() { val apiService = mockk<ApiService>() val userIdSlot = slot<Int>() // Stub the method to capture the argument every { apiService.sendUserId(capture(userIdSlot)) } just runs // Call the method apiService.sendUserId(42) // Verify the captured value assertEquals(42, userIdSlot.captured) } }
Job Offers
Key Points
- Use
capture(userIdSlot)
to store the argument passed to the method. - You can inspect the captured value with
userIdSlot.captured
.
Unit Testing Android Components with MockK
MockK can also help you test Android components like ViewModel
, LiveData
, Activity
, and Fragment
. Here’s a quick overview.
Mocking a ViewModel
Let’s say you have a UserViewModel
that fetches user data:
class UserViewModel(private val userRepository: UserRepository) : ViewModel() { val userName = MutableLiveData<String>() fun loadUser(userId: Int) { val name = userRepository.getUser(userId) userName.value = name } }
Testing the ViewModel with MockK
import androidx.arch.core.executor.testing.InstantTaskExecutorRule import androidx.lifecycle.Observer import io.mockk.every import io.mockk.mockk import io.mockk.verify import org.junit.Assert.assertEquals import org.junit.Rule import org.junit.Test class UserViewModelTest { @get:Rule val rule = InstantTaskExecutorRule() @Test fun `test loadUser updates userName LiveData`() { val mockUserRepository = mockk<UserRepository>() val viewModel = UserViewModel(mockUserRepository) val observer = mockk<Observer<String>>(relaxed = true) // Stub the repository every { mockUserRepository.getUser(1) } returns "John Doe" // Observe the LiveData viewModel.userName.observeForever(observer) // Call the function viewModel.loadUser(1) // Verify the LiveData was updated verify { observer.onChanged("John Doe") } } }
Key Points
- Use
InstantTaskExecutorRule
to test LiveData. - Use
mockk<Observer<T>>()
to mock LiveData observers.
Conclusion
Congratulations! You’ve learned how to:
- Mock static methods with
mockkStatic
. - Mock singleton objects with
mockkObject
. - Mock final classes with
mockkClass
. - Use advanced constructs like
slot
andcapture
.
In the next article, we’ll dive into testing asynchronous code with MockK, including coroutines and Flows. Stay tuned, and happy testing!
This article is previously published on proandroiddev.com.