Mockito is a popular mocking framework for JVM languages such as Scala, Java, and Kotlin. It helps you isolate dependencies, define test behavior, and verify interactions with collaborators that would otherwise be slow, complex, or external to the unit under test.
Why Use Mockito?
Mockito is useful in unit tests when you need to:
- Isolate dependencies: Replace real services, repositories, clients, or gateways with mocks.
- Control behavior: Define exactly what a dependency returns or throws for a given input.
- Verify interactions: Check that a dependency was called with the expected arguments.
- Exercise failure paths: Simulate exceptions or unavailable dependencies without relying on fragile infrastructure.
Setting Up Mockito with Scala
Add ScalaTest and Mockito Scala to build.sbt:
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "3.2.16" % Test,
"org.mockito" %% "mockito-scala" % "1.17.14" % Test,
"org.mockito" %% "mockito-scala-scalatest" % "1.17.14" % Test
)
For the examples below, assume a small domain like this:
final case class User(name: String)
trait UserRepository {
def getUserById(id: String): Option[User]
def save(user: User): Unit
}
final class UserService(repository: UserRepository) {
def findUser(id: String): Option[User] =
repository.getUserById(id)
def register(user: User): Unit =
repository.save(user)
}
Creating a Mock
Use mock[T] to create a mock of type T.
import org.mockito.MockitoSugar
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class MyServiceTest extends AnyFlatSpec with Matchers with MockitoSugar {
"UserRepository" should "return a stubbed user" in {
val mockRepo = mock[UserRepository]
when(mockRepo.getUserById("123")).thenReturn(Some(User("John")))
mockRepo.getUserById("123") shouldBe Some(User("John"))
}
}
In this example:
mock[UserRepository]creates a mock repository.when(mockRepo.getUserById("123")).thenReturn(...)defines the return value for a specific call.
Verifying Interactions
After calling code that depends on a mock, use verify to assert that an interaction happened.
import org.mockito.MockitoSugar
import org.scalatest.flatspec.AnyFlatSpec
class UserServiceTest extends AnyFlatSpec with MockitoSugar {
"UserService" should "call the repository" in {
val mockRepo = mock[UserRepository]
val service = new UserService(mockRepo)
service.findUser("123")
verify(mockRepo).getUserById("123")
}
}
verify(mockRepo).getUserById("123") checks that the repository was called with the exact argument "123".
Stubbing Exceptions
You can stub a method to throw an exception when testing error handling.
when(mockRepo.getUserById("invalidId"))
.thenThrow(new RuntimeException("User not found"))
Use this when the behavior you want to test depends on failures from a dependency.
Argument Matchers: any, eqTo, and argThat
Mockito provides argument matchers for flexible stubbing and verification.
any
any[T] matches any value of the specified type.
when(mockRepo.getUserById(any[String]))
.thenReturn(Some(User("John")))
Here, getUserById returns Some(User("John")) for any string argument.
eqTo
eqTo(value) matches one exact value. In Mockito Scala, eqTo is usually preferred over Java Mockito's eq because it avoids conflicts with Scala's eq method.
verify(mockRepo).getUserById(eqTo("expectedId"))
argThat
argThat lets you match arguments with custom logic.
verify(mockRepo).getUserById(argThat[String](_.startsWith("user_")))
This verifies that getUserById was called with an ID starting with "user_".
Matcher Example
when(mockRepo.getUserById(any[String]))
.thenReturn(Some(User("John")))
service.findUser("user_123")
verify(mockRepo).getUserById(argThat[String](_.startsWith("user_")))
Verifying Call Counts
Use call-count verification when the number of interactions matters.
val mockRepo = mock[UserRepository]
val service = new UserService(mockRepo)
service.findUser("123")
service.findUser("123")
verify(mockRepo, times(2)).getUserById("123")
Other useful call-count helpers include:
verify(mockRepo, never()).getUserById("invalidId")
verify(mockRepo, atLeast(1)).getUserById("123")
verify(mockRepo, atMost(3)).getUserById("123")
Capturing Arguments with ArgumentCaptor
ArgumentCaptor captures values passed to a mock so you can inspect them after verification.
import org.mockito.ArgumentCaptor
import org.mockito.MockitoSugar
import org.scalatest.flatspec.AnyFlatSpec
import org.scalatest.matchers.should.Matchers
class ArgumentCaptorExampleTest
extends AnyFlatSpec
with Matchers
with MockitoSugar {
"UserService" should "capture saved users" in {
val mockRepo = mock[UserRepository]
val service = new UserService(mockRepo)
service.register(User("Ada"))
val captor = ArgumentCaptor.forClass(classOf[User])
verify(mockRepo).save(captor.capture())
captor.getValue shouldBe User("Ada")
}
}
Use captors when the exact argument is built inside the code under test and cannot be conveniently asserted before the call.
Stubbing Unit Methods
For Scala methods returning Unit, use doThrow or doAnswer patterns instead of ordinary when(...).thenReturn(...).
doThrow(new RuntimeException("write failed"))
.when(mockRepo)
.save(User("Ada"))
This is useful for testing how your service behaves when a write operation fails.
Verifying No More Interactions
Use verifyNoMoreInteractions to ensure only the expected calls occurred.
service.findUser("123")
verify(mockRepo).getUserById("123")
verifyNoMoreInteractions(mockRepo)
Use this sparingly. It can make tests brittle if implementation details change without changing behavior.
Resetting Mocks
reset clears all stubbing and interaction history.
reset(mockRepo)
Prefer creating a fresh mock per test instead of resetting one mock repeatedly. Fresh mocks are easier to reason about.
In-Order Verification
InOrder verifies that calls happened in a specific sequence.
val inOrder = inOrder(mockRepo)
service.findUser("123")
service.findUser("456")
inOrder.verify(mockRepo).getUserById("123")
inOrder.verify(mockRepo).getUserById("456")
Use in-order verification only when order is part of the contract.
Spies and Partial Mocking
A spy wraps a real object. Real methods run unless you stub them.
val realService = new UserService(mockRepo)
val spyService = spy(realService)
doReturn(Some(User("Mocked")))
.when(spyService)
.findUser("123")
spyService.findUser("123") // returns Some(User("Mocked"))
Spies are powerful, but they can couple tests tightly to implementation details. Prefer ordinary mocks for dependencies and real instances for the class under test.
Timeout Verification
timeout verifies that a method is called within a time window.
verify(mockRepo, timeout(100)).getUserById("123")
This is mainly useful for asynchronous code. Keep timeouts short and avoid relying on timing unless the behavior is inherently asynchronous.
Mockito Cheat Sheet
| Operation | Description |
|---|---|
mock[T] |
Creates a mock object. |
when(...).thenReturn(...) |
Stubs a method to return a specific value. |
thenThrow(...) |
Stubs a method to throw an exception. |
verify(...) |
Verifies that a method was called with expected arguments. |
times(n) |
Verifies that a method was called exactly n times. |
never() |
Verifies that a method was never called. |
atLeast(n) / atMost(n) |
Verifies minimum or maximum call counts. |
any[T] |
Matches any argument of type T. |
eqTo(value) |
Matches an exact argument value. |
argThat(...) |
Matches an argument with custom logic. |
ArgumentCaptor |
Captures method arguments for later inspection. |
doThrow(...) |
Stubs failures for Unit or difficult-to-stub methods. |
verifyNoMoreInteractions(...) |
Verifies that no extra interactions occurred. |
reset(...) |
Clears stubbing and verification history. |
inOrder(...) |
Verifies interaction order. |
spy(...) |
Partially mocks a real object. |
timeout(...) |
Verifies that a call happened within a time limit. |
Summary
Mockito is most valuable when it keeps tests focused on one unit of behavior. Use mocks to isolate dependencies, stubs to control collaborator behavior, and verification to assert important interactions. Keep tests readable by mocking only the boundaries that matter, and prefer real values for simple domain objects.