Mockito-Scala: Mocking in Scala Tests
Testing code that depends on external services, databases, or complex collaborators requires isolation — you want to test one unit at a time. Mockito-Scala brings Mockito's mocking capabilities to Scala with an idiomatic API that handles Scala-specific features like default arguments, final classes, and type inference.
Why Mockito-Scala Over Plain Mockito?
Java's Mockito works in Scala but has friction points:
when(mock.method())doesn't work well with Scala's type inference- Scala default parameters cause issues
any()matchers returnnullinstead of default values, causing NullPointerExceptions- Verifying multiple calls is verbose
Mockito-Scala wraps Mockito with a Scala-friendly API that solves all of these.
Installation
libraryDependencies ++= Seq(
"org.mockito" %% "mockito-scala" % "1.17.37" % Test,
// Optional ScalaTest integration
"org.mockito" %% "mockito-scala-scalatest" % "1.17.37" % Test
)Creating Mocks
import org.mockito.MockitoSugar
class UserServiceTest extends AnyFlatSpec with MockitoSugar with Matchers {
// Create a mock
val userRepo: UserRepository = mock[UserRepository]
val emailService: EmailService = mock[EmailService]
// Create service with mocked dependencies
val userService = new UserService(userRepo, emailService)
}MockitoSugar is the main entry point for mock creation and stubbing.
Stubbing Method Returns
// Return a value
when(userRepo.findById(42)) thenReturn Some(User(42, "Alice"))
// Return None
when(userRepo.findById(99)) thenReturn None
// Throw an exception
when(userRepo.findById(-1)) thenThrow new IllegalArgumentException("negative id")
// Return different values on consecutive calls
when(userRepo.nextId())
.thenReturn(1)
.thenReturn(2)
.thenReturn(3)
// Use Answer for complex logic
when(userRepo.save(any[User])) thenAnswer { invocation =>
val user = invocation.getArgument[User](0)
user.copy(id = Some(generateId()))
}Argument Matchers
import org.mockito.ArgumentMatchers._
// Match any argument
when(userRepo.findById(any[Int])) thenReturn None
// Match specific values
when(userRepo.findByEmail(eqTo("alice@example.com"))) thenReturn Some(user)
// Match by predicate
when(userRepo.findByAge(argThat((n: Int) => n >= 18))) thenReturn List(user)
// String matchers
when(userRepo.search(startsWith("Al"))) thenReturn List(alice)
when(userRepo.search(contains("@"))) thenReturn List(alice, bob)Important: When using matchers, ALL arguments must use matchers (or eqTo for specific values):
// Wrong — mixing literal and matcher
when(service.method(42, any[String])) thenReturn "result"
// Correct
when(service.method(eqTo(42), any[String])) thenReturn "result"Verifying Interactions
After exercising your code, verify mocks were called as expected:
test("creates user and sends welcome email") {
when(userRepo.save(any[User])) thenReturn User(1, "Alice", "alice@example.com")
userService.register("Alice", "alice@example.com")
// Verify called once
verify(userRepo).save(User(0, "Alice", "alice@example.com"))
verify(emailService).sendWelcome("alice@example.com")
}
// Verify call count
verify(userRepo, times(3)).findById(any[Int])
verify(emailService, atLeastOnce).sendWelcome(any[String])
verify(emailService, never).sendAlert(any[String])
// Verify no interactions
verifyNoInteractions(emailService)
verifyNoMoreInteractions(userRepo)Captor — Capturing Arguments
Capture arguments to make detailed assertions:
import org.mockito.ArgumentCaptor
test("passes correct user to repository") {
val userCaptor = ArgumentCaptor.forClass(classOf[User])
userService.register("Alice", "alice@example.com", role = "admin")
verify(userRepo).save(userCaptor.capture())
val savedUser = userCaptor.getValue
savedUser.name should be("Alice")
savedUser.role should be("admin")
savedUser.createdAt should not be null
}With Mockito-Scala's idiomatic API:
import org.mockito.captor.ArgCaptor
val captor = ArgCaptor[User]
verify(userRepo).save(captor)
captor.value.name should be("Alice")ScalaTest Integration
The IdiomaticMockito trait provides a cleaner, more Scala-like syntax:
import org.mockito.scalatest.IdiomaticMockito
class UserServiceSpec extends AnyFlatSpec
with Matchers
with IdiomaticMockito {
"UserService.register" should "save user and send email" in {
val userRepo = mock[UserRepository]
val emailService = mock[EmailService]
val service = new UserService(userRepo, emailService)
// Idiomatic stubbing
userRepo.save(*) returns User(1, "Alice", "alice@example.com")
emailService.sendWelcome(*) doesNothing()
service.register("Alice", "alice@example.com")
// Idiomatic verification
userRepo.save(*) wasCalled once
emailService.sendWelcome("alice@example.com") wasCalled once
}
}The * wildcard is Mockito-Scala's idiomatic alternative to any().
Mocking Final Classes and Objects
Scala classes are final by default. Mockito-Scala handles this with inline mocking (requires no ByteBuddy configuration on newer JVMs):
// In test/resources/mockito-extensions/org.mockito.plugins.MockMaker
// Add: mock-maker-inline
// Then in tests
val finalClass = mock[MyFinalClass]
when(finalClass.method()) thenReturn "result"Or use the @MockitoSettings annotation:
@MockitoSettings(strictness = Strictness.LENIENT)
class MyTest extends AnyFlatSpec with MockitoSugar { ... }Spies — Partial Mocking
A spy wraps a real object, letting you stub specific methods while using the real implementation for others:
val realService = new UserService(realRepo, realEmailer)
val spy = spyLambda(realService) // or spy(realService) for non-final classes
// Only stub the slow external call
when(spy.fetchExternalProfile(any[String])) thenReturn Profile.default
// Everything else uses the real implementation
val user = spy.register("Alice", "alice@example.com")
user.name should be("Alice") // Real logic ran
verify(spy).fetchExternalProfile("alice@example.com") // Stubbed method was calledReturnsDeepStubs
For chained method calls (use sparingly — it often signals a design smell):
val config = mock[AppConfig](ReturnsDeepStubs)
when(config.database.primary.host) thenReturn "localhost"
when(config.database.primary.port) thenReturn 5432Reset Mocks Between Tests
If sharing mocks across tests:
afterEach {
reset(userRepo, emailService)
}Or better, create fresh mocks in each test to avoid state leakage.
Common Mistakes
Stubbing after the call: Always stub before the code under test runs.
Mixing matchers and literals: Use eqTo() when combining with other matchers.
Verifying what you stubbed: If you stubbed findById(42) and verify findById(42), you've proved nothing — you just confirmed Mockito tracked the call. Verify interactions your code should make, not interactions that are forced by stubs.
Over-mocking: Don't mock value objects, data classes, or simple utilities. Mock boundary objects — repositories, HTTP clients, email services.
End-to-End Complement
Mockito-Scala isolates units for fast, focused tests. For verifying that your Scala application's user-facing features work end-to-end — with real HTTP, real rendering, and real browser interactions — HelpMeTest provides continuous browser monitoring. Unit mocking tests logic correctness; browser testing tests user experience.
Summary
Mockito-Scala makes mocking idiomatic in Scala:
mock[T]creates mocks for traits and classeswhen(...) thenReturnstubs behaviorverify(mock).method(args)confirms interactionsIdiomaticMockitoprovides cleaner syntax withreturns,wasCalled- Argument captors inspect what was passed to collaborators
- Spies partially mock real objects
Keep mocks focused on boundary objects, keep stubs minimal, and verify only the interactions your design requires — that's the recipe for maintainable test suites.