

As mobile applications grow in complexity, testing individual components in isolation becomes increasingly important. Unit testing helps developers validate logic early, but dependencies between methods and classes can make tests harder to write, maintain, and reason about.
In Kotlin-based Android development, tools like Mockk simplify this challenge by allowing developers to mock or spy on dependencies. At the same time, teams often need broader confidence that their applications behave correctly from a user’s perspective—across devices, OS versions, and real workflows. This is where unit testing and end-to-end testing come together, with platforms like Tricentis Testim Mobile complementing code-level tests through scalable, AI-powered mobile test automation.
In this article, you’ll learn what Mockk Spy is, how it works, and how to use it effectively in your Kotlin unit tests.
What is Mockk?
Mockk is a mocking library with first-class support for Kotlin.
You can also define Mockk as an alternative to Mockito, which is a popular mocking framework for Java.
Mockk improves on Mockito in terms of Kotlin usage in several ways. For example, Mockk makes it easier to mock final classes.
Mockk Spy makes it possible to customize the behavior of methods in the object you’re mocking while still being capable of running the actual method.
What is Spy in Mockk?
Mockk Spy makes it possible to customize the behavior of methods in the object you’re mocking while still being capable of running the actual method. In testing, a spy generally refers to a real instance of a class or object that can be stubbed.
Mockk provides a spyk() function that you can use to create a new spy instance of a class.
Before we continue to the tutorial on how to use Mockk in tests, let’s discuss some of the differences between spies and stubs.
Differences between spies and stubs in unit testing
The following are some differences between spies and stubs.
- A spy represents an instance of an object with a copy of all the properties in the object. A stub, on the other hand, contains steps that determine how tests respond to method calls.
- It’s possible to call methods in a spy object without altering their behavior, while a stub only provides a temporary replacement to the behavior of a method.
- Stubs and spies work together such that methods in a spy can be stubbed.
How to use Mockk Spy in a test
Now, let’s walk through a guide on how to use Mockk Spy in a test. For this tutorial, we’ll be using Mockk to write unit tests for an Android application written in Kotlin.
Step 1: Project setup
In order to follow along, you’ll first need to create a new Android Studio project. Alternatively, you can use an existing project that you already have on your computer.
Once you have your project, create a new Kotlin class under the main package and save it as Utils.
Add the following methods to the new class:
fun add(a: Int, b: Int): Int {
return a+b
}
fun findAverage(a: Int, b: Int): Int {
val total = add(a,b)
return total/2;
}
From the code sample above, the first method simply adds two numbers. The second method finds the average value of two numbers. That is to say, it uses the first add() method to add the parameters, then it divides the result by 2.
Later in this guide, we’ll see how we can create a spy for this class.
Step 2: Add Mockk dependency to your project
Before you can use Mockk, you’ll need to add the Gradle dependency to your project. To do this, open the app-level build.gradle file for your project and add the following code to the dependencies section:
dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation 'io.mockk:mockk:1.13.3'
}
Once you’re done, hit the Sync Now button that appears after modifying the Gradle file to finish the installation.
Step 3: Write unit test with Spy
Now that we have Mockk installed and the class we wish to test, let’s write our first test. Open the test source set folder and create a new class for our unit tests. Save the class as UtilsTest.
Next, add the following method with the @Test annotation to the class so that the code for the class looks like this:
class UtilsTest {
@Test
fun testSpy() {
//Arrange
val spy = spyk<Utils>()
//Act
val result = spy.findAverage(5,5)
//Assert
assertEquals(5, result)
}
}
The code above sets up a unit test that builds a spy of the Utils class. Next, the result variable fires the spy copy of the findAverage() method. Then, the last line verifies whether the result meets an expectation of 5.
This test should pass, as calling findAverage() will return the result of adding 5 plus 5, which is 10, and 10 divided by 2 equals 5.
From the test above, we created a spy but didn’t alter the behavior of any of the methods. So, let’s write another test demonstrating how to use a spy to change the behavior of a method.
Add the following method just after the testSpy() method:
@Test
fun testSpyCustom() {
//Arrange
val spy = spyk<Utils>()
every { spy.add(any(), any()) } returns 10
//Act
val result = spy.findAverage(15,5)
//Assert
Assert.assertEquals(5, result)
}
The testSpyCustom() method is very similar to the previous method except for the introduction of the line with “every { spy.add(any(), any()) } returns 10“.
This line means that for every time the add() function is called, your test will ignore the original implementation of the function and return the value 10 instead. As a result, this test will always pass no matter the value you pass to the findAverage() method.
Step 4: Run the tests
To run a test, simply tap on the green play button next to each test method. Then, wait for Android Studio to finish running the test.
If you wish to run all tests at once, you can hit the green play button near the class declaration instead.
Mockk makes it easy to test private methods
Step 5: Testing private methods in Mockk
Mockk makes it easy to test private methods. In order to demonstrate this, let’s add a new private subtract method to the Utils class by adding the following after the findAverage() method:
private fun subtract(a: Int, b: Int): Int {
return a-b;
}
fun inverseSubtract(a: Int, b: Int): Int {
return subtract(b, a)
}
Now, add a new test for this private method in the UtilsTest.kt file using the following code:
@Test
fun testPrivateMethod() {
// Arrange
val mock = spyk<Utils>(recordPrivateCalls = true)
every { mock["subtract"](any<Int>(), any<Int>()) } answers { firstArg<Int>() - 10 }
// Act
val result = mock.inverseSubtract(1, 30)
// Assert
assertEquals(20, result)
}
The code above creates a mock capable of stubbing the private method by setting the value of the recordPrivateCalls parameter to true.
Then, in order to access and alter the behavior of the private method, the name of the method is provided as a string inside the square brackets.
Next, the behavior is altered such that on every call of the subtract method, it returns a-10 instead of a-b.
Conclusion
In this article, we explored Mockk and Mockk Spy, covering how spies differ from stubs and how to use them to control method behavior in Kotlin unit tests. Through practical examples, you learned how to spy on public and private methods, override logic selectively, and validate dependent behavior effectively.
While Mockk is powerful for unit and instrumentation testing, these tests still require coding expertise and ongoing maintenance. For teams looking to extend coverage beyond unit tests and validate real user flows, Tricentis Testim Mobile offers a complementary approach. With AI-powered mobile test automation and support for both coded and no-code testing, Tricentis Testim Mobile enables teams to test Android and iOS applications at scale—without slowing down development.
By combining code-level tools like Mockk with end-to-end testing through Tricentis Testim Mobile, teams can achieve faster feedback, broader coverage, and greater confidence in production releases.