By WebNest Studio Engineering Team
Advanced Java Tutorial
Mockito and Testing Spring Applications
A unit test for a service class shouldn't also need a real database, a real HTTP call to another service, or a fully started Spring application — any of those turns a fast, focused unit test into a slow, brittle integration test. Mockito is the standard Java mocking framework: it creates fake ("mock") implementations of a class's dependencies, so you can test one class's logic completely in isolation.
Why it matters
A service that calls a database or another API cannot be unit-tested as it stands: the test would be slow and would fail whenever the dependency was unavailable. A mock stands in for the dependency and returns what the test tells it to. Mockito is the standard tool for this in Java and is assumed knowledge in Spring projects.
Mock vs. Stub vs. Spy
- Stub — an object that returns pre-programmed, canned answers; a test doesn't verify how it was called, only that it supplied the data needed.
- Mock — a fake object whose interactions you explicitly verify: how many times a method was called, and with what arguments.
- Spy — wraps a real object, letting its real methods run by default, with the option to override specific method calls — useful when you want mostly-real behavior with one method faked.
The Core Mockito API
@Mock creates a fake instance of a dependency's type. @InjectMocks creates a real instance of the class under test and automatically injects the @Mock-annotated fields into it (via constructor or setter injection, whichever the class supports). when(...).thenReturn(...) programs a mock's canned response. verify(mock, times(n)).method(...) asserts a method was actually called the expected number of times with the expected arguments.
Testing a Spring Boot Application at Different Levels
Spring Boot Test offers focused testing slices instead of forcing every test to load the entire application context. @ExtendWith(MockitoExtension.class) (no Spring context at all) is the fastest option for pure business-logic unit tests. @WebMvcTest(SomeController.class) loads only the web layer, letting you test a controller's request/response handling with MockMvc while mocking out its service dependencies with @MockBean. @SpringBootTest loads the full application context and is reserved for true integration tests, since it's significantly slower to start than the more targeted slices.
Example
A pure Mockito unit test for a service, and a @WebMvcTest slice test for a controller
// Pure unit test — no Spring context, fastest to run
@ExtendWith(MockitoExtension.class)
class StudentServiceTest {
@Mock private StudentRepository repo;
@InjectMocks private StudentService service;
@Test
void toppersReturnsOnlyStudentsAboveNinety() {
when(repo.findByMarksGreaterThan(90))
.thenReturn(List.of(new Student(1, "Vansh", 95)));
List<Student> result = service.toppers();
assertEquals(1, result.size());
assertEquals("Vansh", result.get(0).getName());
verify(repo, times(1)).findByMarksGreaterThan(90);
}
}
// Web-layer slice test — loads only Spring MVC infrastructure, mocks the service
@WebMvcTest(StudentController.class)
class StudentControllerTest {
@Autowired private MockMvc mvc;
@MockBean private StudentService service;
@Test
void getByIdReturns200WithJsonBody() throws Exception {
when(service.findById(1)).thenReturn(Optional.of(new Student(1, "Vansh", 95)));
mvc.perform(get("/api/students/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Vansh"));
}
@Test
void getByIdReturns404WhenMissing() throws Exception {
when(service.findById(99)).thenReturn(Optional.empty());
mvc.perform(get("/api/students/99"))
.andExpect(status().isNotFound());
}
}
StudentServiceTest > toppersReturnsOnlyStudentsAboveNinety() PASSED
StudentControllerTest > getByIdReturns200WithJsonBody() PASSED
StudentControllerTest > getByIdReturns404WhenMissing() PASSED
Common Mistakes
- Reaching for @SpringBootTest (full application context) for every test, making the suite slow, when a plain Mockito test or a focused @WebMvcTest slice would run in a fraction of the time.
- Mocking a class instead of an interface unnecessarily, or mocking value objects/DTOs that have no real behavior worth faking.
- Verifying interactions Mockito already implies by the test passing (over-specifying with verify() on every single call), making tests brittle to harmless refactors.
- Forgetting that @Mock fields return null (or 0/false) by default until explicitly stubbed with when(...).thenReturn(...), causing confusing NullPointerExceptions in the test itself.
Exercise
This exercise needs JUnit 5 and Mockito on the classpath, so it does not run in the playground. UserService.displayName looks a user up in UserRepository and returns the name in upper case, or UNKNOWN when there is no such user. Write a test that mocks the repository, stubs findName(1L) to return asha, checks the result, and verifies that the repository was called.
Expected result: the test passes without any database.
import java.util.Optional;
interface UserRepository {
Optional<String> findName(long id);
}
class UserService {
private final UserRepository repository;
UserService(UserRepository repository) {
this.repository = repository;
}
String displayName(long id) {
return repository.findName(id).map(String::toUpperCase).orElse("UNKNOWN");
}
}
// UserServiceTest.java
// TODO: a test class using MockitoExtension, a mocked repository and the service under test
Show hints
@Mockcreates the fake and@InjectMocksbuilds the service with it; both need@ExtendWith(MockitoExtension.class)on the class.- Stub with
when(repository.findName(1L)).thenReturn(Optional.of("asha"))and check the call withverify(repository).findName(1L).
Show solution
import java.util.Optional;
interface UserRepository {
Optional<String> findName(long id);
}
class UserService {
private final UserRepository repository;
UserService(UserRepository repository) {
this.repository = repository;
}
String displayName(long id) {
return repository.findName(id).map(String::toUpperCase).orElse("UNKNOWN");
}
}
// UserServiceTest.java
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository repository;
@InjectMocks
private UserService service;
@Test
void returnsTheNameInUpperCase() {
when(repository.findName(1L)).thenReturn(Optional.of("asha"));
assertEquals("ASHA", service.displayName(1L));
verify(repository).findName(1L);
}
}
Quick quiz
1. What does a mock return from a method that has not been stubbed?
- A. It throws an exception
- B. It calls the real method
- C. A default value: null, zero, false or an empty collection or Optional
- D. A random value
Show answer
C. Behaviour is defined explicitly with when(...).thenReturn(...).
2. What does verify(repository).save(user) check?
- A. That save was called with that argument
- B. That save returned a value
- C. That save is public
- D. That save was never called
Show answer
A. It asserts on an interaction instead of on a returned value.
3. What is the difference between a mock and a spy?
- A. There is none
- B. A spy cannot be verified
- C. A mock is slower
- D. A mock is a complete fake; a spy wraps a real object and runs its real methods unless they are stubbed
Show answer
D. A spy is used when most of the real behaviour should be kept.
Interview questions
Why use mocks in unit tests?
Show answer
A unit test should check one class in isolation. Its real dependencies, such as a database, an HTTP client or an email sender, are slow, need infrastructure and can fail for unrelated reasons. A mock replaces the dependency with an object the test controls: it returns chosen values, can simulate errors that are hard to cause for real, and records calls so the test can verify them. The test is then fast and deterministic.
What is the difference between @Mock and Spring's mock bean annotation?
Show answer
@Mock is plain Mockito: it creates a mock in a unit test with no Spring context at all, which is the fastest kind of test. Spring's annotation, @MockitoBean in current versions and @MockBean in older ones, creates a mock and places it in the Spring application context, replacing the real bean. It is used in tests that start a context, such as @WebMvcTest, where the controller's collaborators must be faked.
Key Points to Remember
- Stub returns canned data; Mock verifies interactions; Spy wraps a real object with selective overrides.
- @Mock creates fakes, @InjectMocks wires them into the real class under test automatically.
- when(...).thenReturn(...) programs mock behavior; verify(...) asserts how a mock was actually called.
- Match the test type to what you're verifying: plain Mockito for logic, @WebMvcTest for the web layer, @SpringBootTest only for true integration tests.
Practice in the playground
Write and run your own code for this topic, or start from the exercise or an example. Runs plain Java 17. Frameworks such as Spring, and databases, are not available here. Your code for each topic is kept in this browser.
Run the examples
Change an input, predict the result, then compare it with the output. Explain why the result changes.
These examples need a JDK project with their libraries on your computer, so they can be copied but not run here. Codelab runs Python, FastAPI backends with SQLite, plain Java 17 and HTML/CSS/JavaScript.
Written by
WebNest Studio Engineering Team
Practical lessons from developers working with Java, Python, React, databases and production systems.
Led by Vansh Duggal, Founder, WebNest Studio · Full-stack & software engineering