Testing Frameworks
Doing testing requires two things working together:
- A Testing Framework serves as the conductor. It tells your tests when to run, how to organize them, and reports the results. Think of it as the test runner.
- A Mocking Framework acts as the understudy. It lets you replace real dependencies (databases, external services) with fake versions you control.
Spring Boot's Out-of-the-Box Solution
Spring Boot gives you a solid foundation with spring-boot-starter-webmvc-test (spring-boot-starter-test if you're still on Spring Boot 3).
This single dependency pulls in everything you need:
| Framework | Purpose | Why It Matters |
|---|---|---|
| JUnit 5 | Test runner | The de facto standard for Java testing. Runs your tests, handles lifecycle (before/after), integrates with IDEs and CI pipelines. |
| Mockito | Mocking | Creates fake objects that behave like real ones. Essential for isolating the code you're testing. |
| AssertJ | Assertions | "Fluent" assertions that read like English. assertThat(x).isEqualTo(y) beats assertEquals(x, y) any day. |
| Hamcrest | Matchers | Another assertion library with expressive matchers. AssertJ is generally preferred. |
This is your bread and butter. @SpringBootTest starts the full application context (useful for integration tests), @MockBean creates a Mockito mock and injects it where needed, and JUnit's @Test marks the method as a test.
Notice the AssertJ chain: assertThat(result.getTitle()).isEqualTo("ACADEMY DINOSAUR"). It reads almost like a sentence, which makes debugging way less painful when you're staring at a failing test at 2 AM.
Kotlin MockK
Mockito was designed for Java. Kotlin's language features create several friction points that Mockito simply wasn't built to handle.
- Final Classes by Default: In Kotlin, all classes are
finalby default. Mockito (historically) couldn't mock final classes without plugins and workarounds. - Coroutines: If you're using Kotlin coroutines (suspend functions), Mockito has no idea what to do with them. It treats them as regular methods, which means your async code tests become synchronous by accident.
- The
whenKeyword:whenis a reserved keyword in Kotlin. - Object/Static Mocking: Kotlin
objectdeclarations are singletons. Mocking them in Mockito is painful.
MockK was created specifically for Kotlin, and it shows in every API decision:
Clean, readable, and no backticks needed. The every keyword replaces when, and everything feels Kotlin-native.
Groovy Spock
If you're open to writing your tests in Groovy, Spock is a game-changer. Spock doesn't just replace Mockito, it replaces JUnit too. It's a complete testing framework that includes:
- Test Runner works like JUnit, but with superpowers
- Mocking Engine works like Mockito, but built into the language
- Assertion Engine provides capabilities unlike anything you've seen from Java libraries
- BDD Structure forces you to write tests as stories
Look at that structure: given:, when:, then:. It forces you to organize your tests into a narrative. Given some setup, when an action happens, then these are the expected outcomes. Your future self (or the poor developer who inherits your code) will thank you.
The mocking syntax is refreshingly simple too:
1 * filmRepository.findById(1L) >> Optional.of(mockFilm)
This reads as: "Expect exactly 1 call to findById(1L), and return this mock film." No boilerplate, no verbosity.
Which One Should You Choose?
There's no wrong choice, but there are better choices for your context.
The best test is the one you write. Pick the tool that makes you want to write tests, and you'll end up with a better test suite than if you'd forced yourself to use the "correct" tool.