When you're testing an application, you often run into situations where your code depends on other components like services, repositories, external APIs, or even third-party libraries. Instead of running the real code of those dependencies in tests (and wasting a bunch of resources), we can use mocks — "fakes" that emulate the behavior of real objects. And that's where Mockito comes in.
Picture this: you're writing a test for a service that calls a database. A real call to the database could take too long, and it might even introduce SQL-dependency issues... that happens. Instead, with Mockito, we just replace the DB call with a special object that returns a pre-set result. Handy, right?
Mockito core concepts
Before jumping into practical stuff, let's unpack the terms mocks, stubs, and spies.
- Mock — an object that imitates the behavior of a real object. With it you can define predefined responses to specific calls.
- Stub — similar to a mock but less flexible. It's basically a "plain" object with preprogrammed behavior used for testing.
- Spy — a real object, but with the ability to observe how it's used. Sometimes you need the real functionality but want to override parts of its behavior.
Getting started with Mockito
To use Mockito, add its dependency to your project.
Adding Mockito via Maven
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>5.0.0</version> <!-- Replace with the current version -->
<scope>test</scope>
</dependency>
If you use Gradle:
testImplementation 'org.mockito:mockito-core:5.0.0' // Replace with the current version
Also make sure to include JUnit if it's not already in the project.
Main features of Mockito
1. Creating mocks
You can create a mock using the mock() method:
import static org.mockito.Mockito.*;
SomeService someServiceMock = mock(SomeService.class);
However, if you're using JUnit 5, we recommend using the @Mock annotation for better readability. You'll need @ExtendWith(MockitoExtension.class) for that.
Example:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
public class SomeServiceTest {
@Mock
private SomeService someServiceMock;
// Your tests go here
}
2. Configuring mock behavior
After creating a mock, the interesting part is defining its behavior. For example, we want a method to return a specific value:
when(someServiceMock.someMethod()).thenReturn("Mocked Value");
Usage example:
@Test
void testMockBehavior() {
when(someServiceMock.greet("John")).thenReturn("Hello, mocked John!");
String result = someServiceMock.greet("John");
assertEquals("Hello, mocked John!", result);
}
when().thenReturn() literally lets you "teach" the mock what it should do.
3. Verifying calls
Mockito lets you not only define behavior but also verify whether a specific method was called — and how many times.
@Test
void testMethodInvocation() {
someServiceMock.greet("John");
verify(someServiceMock).greet("John"); // Check: was greet called with "John"
verify(someServiceMock, times(1)).greet("John"); // Check: called exactly once
}
Mockito annotations
To make code simpler, Mockito provides several handy annotations:
- @Mock: Creates a mock for the specified object.
@Mock private SomeService someServiceMock; - @InjectMocks: Automatically injects mocks into the dependencies of the object under test.
@InjectMocks private SomeController someController;Example:
@Test void testController() { when(someServiceMock.greet("John")).thenReturn("Hello, John!"); String response = someController.greet("John"); assertEquals("Hello, John!", response); } - @Spy: Creates a real object but allows overriding some of its methods.
@Spy private SomeService realService;
Practice: testing a service
We have a service GreetingService that depends on TimeProvider to form a greeting based on the current time:
public class GreetingService {
private final TimeProvider timeProvider;
public GreetingService(TimeProvider timeProvider) {
this.timeProvider = timeProvider;
}
public String getGreeting(String name) {
int currentHour = timeProvider.getCurrentHour();
if (currentHour < 12) {
return "Good morning, " + name + "!";
} else {
return "Good afternoon, " + name + "!";
}
}
}
In tests we mock TimeProvider to verify the service behavior:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class GreetingServiceTest {
private final TimeProvider timeProviderMock = mock(TimeProvider.class);
private final GreetingService greetingService = new GreetingService(timeProviderMock);
@Test
void testMorningGreeting() {
when(timeProviderMock.getCurrentHour()).thenReturn(9); // Morning
String greeting = greetingService.getGreeting("John");
assertEquals("Good morning, John!", greeting);
}
@Test
void testAfternoonGreeting() {
when(timeProviderMock.getCurrentHour()).thenReturn(15); // Afternoon
String greeting = greetingService.getGreeting("John");
assertEquals("Good afternoon, John!", greeting);
}
}
TimeProvider can be considered an external dependency that we've mocked.
Common mistakes when using Mockito
Some mistakes can unexpectedly break your tests:
- NullPointerException: You forgot to initialize mocks. Fix: use
@Mockand@ExtendWith(MockitoExtension.class). - Mismatch between behavior and invocation: If the method is called with different parameters than those you specified in
when(). Always verify your argument correctness. - Excessive mock behavior: "Teach" mocks only what you need for the specific test, otherwise your test will become hard to maintain.
Mockito is a powerful tool that makes testing complex systems easier and more convenient. Now you know how to create mocks, configure their behavior, and verify interactions. In the next lectures you'll see how to apply all this for testing controllers and other components of Spring applications.
GO TO FULL VERSION