Introduction
Unit testing is an age-old integral a part of the software program engineering follow. Most profitable software program merchandise use correctly written unit exams; basically, unit testing verifies the correctness of a chunk of code.
Writing code to check code could seem counterintuitive, however it’s an artwork unto itself that offers a developer confidence of their code.
Beneath are the numerous advantages we derive from writing unit exams:
- Catch bugs early in manufacturing code
- Scale back code complexity by having a unit of code doing a particular factor
- Function a superb supply of documentation for a developer to get perception on a undertaking
- Save time (and cash)
- Encourage writing clear and maintainable code with much less coupling
Unit testing in Kotlin tasks
The Kotlin programming language is basically executed within the JVM atmosphere. Its concise and fancy language options have made it well-liked throughout the group, and it’s getting used often in tasks corresponding to Android purposes, Kotlin Multiplatform Cell (KMM) apps, and spring boot purposes.
There are presently two well-liked frameworks constructed to help in efficient unit testing: Mockito and Mockk. On this submit, we’ll speak about every of them by way of the next sections:
Mockk vs. Mockito
Mockk and Mockito are libraries that assist write unit exams that concentrate on JVM platforms. Mockito has been round for the reason that early days of Android growth and ultimately turned the de-facto mocking library for writing unit exams.
Mockito and Mockk are written in Java and Kotlin, respectively, and since Kotlin and Java are interoperable, they’ll exist throughout the similar undertaking. Primarily, each libraries can be utilized interchangeably in these tasks, however the preeminence of Kotlin has tipped the steadiness in favor of Mockk for many Kotlin tasks.
The next factors summarize why Mockk is favored over Mockito for Kotlin tasks:
- Top notch assist for Kotlin options
- A pure Kotlin-mocking DSL for writing clear and idiomatic Kotlin code
- Mocking assist for closing courses and strategies
- Coroutine assist by default
write unit exams for Kotlin tasks
For the aim of this text, we are going to implement a easy person repository class to reveal writing unit exams in a Kotlin undertaking. There are two issues to notice earlier than we proceed:
- The event atmosphere can be in Android Studio
- We’ll examine the syntax distinction between each mocking libraries for the totally different check instances lined
Sufficient speak, allow us to get our arms soiled!
Extra nice articles from LogRocket:
Create the person repository
First, outline the interface for the repository like so:
interface UserRepository { droop enjoyable saveUser(person: Person) droop enjoyable getUser(id: String): Person droop enjoyable deleteUser(id: String) }
That is principally a contract that can be carried out by the concrete
class. See the code block under.
class UserRepositoryImpl constructor( personal val dataSource: DataSource ) : UserRepository { override droop enjoyable saveUser(person: Person) { dataSource.save(person) } override droop enjoyable getUser(id: String): Person { return dataSource.get(id) ?: throw IllegalArgumentException("Person with id $id not discovered") } override droop enjoyable deleteUser(id: String) { dataSource.clear(id) } }
UserRepositoryImpl
has a dependency on DataSource
, by way of which it fulfills the contract by UserRepository
.
DataSource
is an easy Kotlin class. Its function is to retailer person knowledge in reminiscence, the place it could possibly later be retrieved. See the code block under for particulars:
class DataSource { personal val db = mutableMapOf<String, Person>() enjoyable save(person: Person) = db.let { it[user.email] = person } enjoyable get(key: String): Person? = db[key] enjoyable clear(key: String) = db.take away(key) enjoyable clearAll() = db.clear() }
To maintain issues easy, I’ve used a mutableMap
object to avoid wasting a Person
to reminiscence. Maps are collections that holds pairs of objects (keys and values), so it is smart to have the person electronic mail function the distinctive key for saving and retrieving the Person
.
<h3=”add-library-dependencies-gradle”>Add library dependencies to Gradle
Add the next dependencies to your app-level Gradle file, like so:
//Mockk testImplementation "io.mockk:mockk:1.12.4" //Mockito testImplementation "org.mockito:mockito-core:4.0.0" testImplementation "org.mockito:mockito-inline:4.0.0" testImplementation "org.mockito.kotlin:mockito-kotlin:4.0.0"
Utilizing Mockito in a Kotlin undertaking requires some additional dependencies for the next causes:
- Kotlin courses are closing by default and can’t be mocked by Mockito, therefore the necessity for
:mockito-inline:
. You may suppose another can be so as to add the open modifier to the category concerned, however this isn’t advisable as a result of it should mess up your code base and drive you to outline your courses asopen
:mockito-kotlin
is a library that gives useful features for working with Mockito in Kotlin tasks
Writing exams for system below check (SUT) utilizing Mockk
import io.mockk.coEvery import io.mockk.coVerify import io.mockk.mockk import io.mockk.slot import java.util.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.check.runTest import org.junit.Assert import org.junit.Check @OptIn(ExperimentalCoroutinesApi::class) class UserRepositoryImplTest { personal val dataSource = mockk<DataSource>(relaxed = true) personal val sut = UserRepositoryImpl(dataSource) @Check enjoyable `confirm appropriate person params are used`() = runTest { val person = buildUser() sut.saveUser(person) val captor = slot<Person>() coVerify { dataSource.save(seize(captor))} Assert.assertEquals(person.electronic mail, captor.captured.electronic mail) } @Check enjoyable `confirm appropriate person is retrieved`() = runTest { val electronic mail = "[email protected]" coEvery { dataSource.get(any()) } returns buildUser() val person = sut.getUser(electronic mail) Assert.assertEquals(electronic mail, person.electronic mail) } @Check enjoyable `confirm person is deleted`() = runTest { val electronic mail = "[email protected]" sut.deleteUser(electronic mail) coVerify { dataSource.clear(any()) } } companion object { enjoyable buildUser() = Person( id = UUID.randomUUID().toString(), electronic mail = "[email protected]", fullName = "Emmanuel Enya", verificationStatus = Person.VerificationStatus.Verified, memberShipStatus = Person.MemberShipStatus.Free ) } }
The above code block is a reasonably easy check class with minimal check instances. On the high degree of the category physique, I’ve mocked the info supply and created an occasion of the system below check.
Discover that DataSource
is mocked with chill out
set to true
, like so:
mockk<DataSource>(relaxed = true)
This type of mock returns a easy worth for all features, permitting you to skip specifying conduct for every case. Verify the Mockk documentation for extra particulars on relaxed mocks.
Different sections of the code block can be examined side-by-side with the Mockito variant of the check class.
Writing exams for system below check (SUT) utilizing Mockito
import java.util.* import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.check.runTest import org.junit.Assert import org.junit.Check import org.mockito.Mockito.* import org.mockito.kotlin.any import org.mockito.kotlin.argumentCaptor @OptIn(ExperimentalCoroutinesApi::class) class UserRepositoryImplTestMockito { personal val dataSource = mock(DataSource::class.java) personal val sut = UserRepositoryImpl(dataSource) @Check enjoyable `confirm appropriate person params are used`() = runTest { val person = buildUser() sut.saveUser(person) val captor = argumentCaptor<Person>() confirm(dataSource).save(captor.seize()) Assert.assertEquals(person.electronic mail, captor.firstValue.electronic mail) } @Check enjoyable `confirm appropriate person is retrieved`() = runTest { val electronic mail = "[email protected]" `when`(dataSource.get(any())).then { buildUser() } val person = sut.getUser(electronic mail) Assert.assertEquals(electronic mail, person.electronic mail) } @Check enjoyable `confirm person is deleted`() = runTest { val electronic mail = "[email protected]" sut.deleteUser(electronic mail) confirm(dataSource).clear(any()) } companion object { enjoyable buildUser() = Person( id = UUID.randomUUID().toString(), electronic mail = "[email protected]", fullName = "Emmanuel Enya", verificationStatus = Person.VerificationStatus.Verified, memberShipStatus = Person.MemberShipStatus.Free ) } }
The above code block is a check class written with Mockito. On the high degree of the category physique, we now have the mocked dependency and the SUT arrange similarly to how we did with Mockk.
Nonetheless, one salient level to notice is that there is no such thing as a relaxed
mock argument. The rationale for it is because Mockito supplies default solutions for behaviors when they aren’t stubbed.
Conclusions
I really feel Mockk shines right here as a result of mocks usually are not relaxed by default, which inspires you to have whole management over the features name being made by the SUT.
Argument capturing in Mockk and Mockito
argumentCaptor
is a operate from the mockito-kotlin
extension library. It helps to seize a single argument from the mocked object, normally achieved within the verification block.
The Mockk variant is a slot
.
Stubbing
Normally, when writing unit exams, we specify solutions to operate calls on mocked objects. That is known as stubbing in unit testing.
Utilizing Mockito, it’s declared like so:
`when`(dataSource.get(any())).then { buildUser() }
Mockito doesn’t have inbuilt assist for Kotlin coroutines, which implies testing coroutines would require the usage of runTest
for the code to compile.
In Mockk, there’s coEvery { }
for stubbing coroutines and each
{ }
for normal features. This distinction provides readability to the code being examined.
Verification
An vital a part of unit testing is to confirm the tactic interactions of mocked objects in manufacturing code.droop
features can solely be invoked by different droop features. Having coVerify{ }
in place provides builders the assist to stub droop
features, which ordinarily wouldn’t be attainable with the confirm{ }
block.
Mockk supplies two features to check droop features and common features:
coVerify
{ }
, for droop featuresconfirm
{ }
, for normal features
Mockito makes use of confirm()
for verification and nonetheless requires utilizing runTest
to assist droop features. This nonetheless works, however is much less clear than Mockk’s strategy.
Conclusion
We’ve explored unit testing in Kotlin tasks and how one can successfully write exams with Mockk and Mockito.
Be at liberty to make use of whichever library you discover enticing, however I might suggest Mockk due to how versatile it’s when working with the Kotlin language.