Testing is an essential part of software development, ensuring that your code runs as expected before it hits production. In Kotlin, two popular libraries for testing are JUnit and Mockito. JUnit is a powerful framework for creating and running tests, while Mockito is a mocking framework that helps you create test doubles for your dependencies. In this blog, we’ll walk through an example of how to set up testing in Kotlin using JUnit and Mockito.
Setting Up Your Project
Before diving into code, we need to make sure our Kotlin project is set up with the necessary dependencies. If you are using Gradle, you can add the following dependencies to your build.gradle
file:
dependencies { testImplementation 'junit:junit:4.13.2' testImplementation 'org.mockito:mockito-core:3.11.2' testImplementation 'org.mockito:mockito-inline:3.11.2' }
Make sure to sync your project after modifying the build file. This will allow us to use JUnit for writing tests and Mockito for creating mock objects.
Writing Your First Test Case
Let’s say we have a simple Kotlin class that we want to test. Here's a basic Calculator
class:
class Calculator { fun add(a: Int, b: Int): Int { return a + b } fun divide(a: Int, b: Int): Int { if (b == 0) { throw IllegalArgumentException("Divider cannot be zero.") } return a / b } }
Now, we will write a unit test for this class using JUnit. Create a new test file called CalculatorTest.kt
and add the following code:
import org.junit.Assert.assertEquals import org.junit.Test class CalculatorTest { private val calculator = Calculator() @Test fun `test addition`() { val result = calculator.add(3, 4) assertEquals(7, result) } @Test(expected = IllegalArgumentException::class) fun `test division by zero`() { calculator.divide(10, 0) } @Test fun `test division`() { val result = calculator.divide(10, 2) assertEquals(5, result) } }
In this example, we have three test methods in our CalculatorTest
class:
- test addition: Verifies that the
add
method correctly sums two integers. - test division by zero: Checks that an
IllegalArgumentException
is thrown when attempting to divide by zero. - test division: Tests the normal functionality of the
divide
method.
Running Your Tests
You can run your tests directly from your IDE, or you can use the command line by executing:
./gradlew test
JUnit will run all the test cases defined and provide feedback on their outcomes. If everything is correctly implemented, you should see all tests pass.
Mocking with Mockito
In more complex applications, you often deal with dependencies, such as services or repositories, which can make unit testing tricky. This is where Mockito shines. It allows you to create mock implementations of these dependencies so you can test your code in isolation.
Let’s enhance our example by introducing a service that the Calculator
class might use. Suppose we add a MathService
to our application:
interface MathService { fun performOperation(a: Int, b: Int): Int } class Calculator(private val mathService: MathService) { fun add(a: Int, b: Int): Int { return mathService.performOperation(a, b) } }
Now, we need to update our test to mock MathService
. Here’s how to do that with Mockito:
import org.junit.Test import org.mockito.Mockito class CalculatorTest { private val mockMathService = Mockito.mock(MathService::class.java) private val calculator = Calculator(mockMathService) @Test fun `test addition using mocked service`() { Mockito.`when`(mockMathService.performOperation(3, 4)).thenReturn(7) val result = calculator.add(3, 4) assertEquals(7, result) Mockito.verify(mockMathService).performOperation(3, 4) } }
In this updated test, we:
- Created a mock of
MathService
. - Defined what should happen when the
performOperation
method is called with specific parameters usingMockito.when()
. - Verified that the method was called as expected using
Mockito.verify()
.
With this approach, you can test the Calculator
class without relying on a real MathService
implementation, isolating your tests and ensuring fast feedback.
By leveraging JUnit and Mockito in Kotlin, you can create robust, maintainable tests that catch issues before they reach your users. This practice not only boosts your confidence in the code but also leads to a more structured and responsible development process. Happy testing!