Java Testing with JUnit and Mockito with Annotations, Examples, and Implementations
Testing is an integral part of software development. It ensures that the code behaves as expected and helps catch bugs early. In Java, JUnit is a popular framework for unit testing, and Mockito is a powerful library for creating mock objects and performing unit tests for code with dependencies. In this post, I will walk you through everything you need to know about JUnit and Mockito, with all the annotations, examples, and implementations.

1. Introduction to JUnit
What is JUnit?
JUnit is a Java testing framework used to write and run unit tests. Unit tests check if individual units of your code (like methods) are working as expected. The latest version is JUnit 5, and it offers many improvements and more flexibility than JUnit 4.
Why use JUnit?
JUnit helps developers:
- Catch bugs early in the development process.
- Ensure that changes in code don’t break functionality (known as regression testing).
- Write tests quickly and run them automatically.
2. Key Annotations in JUnit
JUnit provides a set of useful annotations to streamline your testing. Here’s a breakdown:
2.1 @Test
- This annotation is used to mark a method as a test case.
- The test method should be public and void.
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Test
public void testAddition() {
Calculator calculator = new Calculator();
int result = calculator.add(2, 3);
assertEquals(5, result, "2 + 3 should equal 5");
}
}
class Calculator {
public int add(int a, int b) {
return a + b;
}
}2.2 @BeforeEach
- This annotation runs a method before each test method.
- It is usually used to set up the environment for each test (initialization).
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
public void setUp() {
calculator = new Calculator();//Initialize calculator before each test
}
@Test
public void testAddition() {
int result = calculator.add(4, 6);
assertEquals(10, result, "4 + 6 should equal 10");
}
}
class Calculator {
public int add(int a, int b) {
return a + b;
}
}2.3 @AfterEach
- This annotation runs a method after each test method.
- It is used to clean up resources after a test is run (e.g., closing database connections).
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
private Calculator calculator;
@BeforeEach
public void setUp() {
calculator = new Calculator();
}
@AfterEach
public void tearDown() {
calculator = null; // Release resources after each test
}
@Test
public void testSubtraction() {
int result = calculator.subtract(10, 5);
assertEquals(5, result, "10 - 5 should equal 5");
}
}
class Calculator {
public int subtract(int a, int b) {
return a - b;
}
}2.4 @BeforeAll
- This annotation runs once before all test methods.
- It is usually used to set up static or expensive resources (e.g., starting a database).
2.5 @AfterAll
- This annotation runs once after all test methods.
- It is used to clean up static or expensive resources after tests have finished (e.g., shutting down a server).
Example for @AfterAll and @BeforeAll:
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class DatabaseTest {
static DatabaseConnection connection;
@BeforeAll
public static void setUpDatabase() {
connection = new DatabaseConnection();
connection.connect();
System.out.println("Database connected");
}
@AfterAll
public static void tearDownDatabase() {
connection.disconnect();
System.out.println("Database disconnected");
}
@Test
public void testConnection() {
assertTrue(connection.isConnected(), "Database should be connected");
}
}
class DatabaseConnection {
private boolean connected = false;
public void connect() { this.connected = true; }
public void disconnect() { this.connected = false; }
public boolean isConnected() { return connected; }
}2.6 @Disabled
- This annotation disables a test method.
- It is useful when a test is incomplete or temporarily broken.
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class CalculatorTest {
@Disabled("Feature not implemented yet")
@Test
public void testMultiplication() {
Calculator calculator = new Calculator();
int result = calculator.multiply(2, 3);
assertEquals(6, result, "2 * 3 should equal 6");
}
}
class Calculator {
public int multiply(int a, int b) {
return a * b;
}
}2.7 @ParameterizedTest
- Used to run the same test with different parameters.
- Works with various sources of input, such as arrays, lists, and streams.
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
public class CalculatorTest {
@ParameterizedTest
@ValueSource(ints = {1, 2, 3, 4})
public void testIsEven(int number) {
assertTrue(number % 2 == 0);
}
}3. Working with Assertions in JUnit
Assertions are methods used to check the result of tests. If the assertion fails, the test fails. Below are common assertions with examples. assertEquals
- Asserts that two values are equal.
Example:
assertEquals(5, calculator.add(2, 3));assertNotEquals
- Asserts that two values are not equal.
Example:
assertNotEquals(6, calculator.add(2, 3));assertTrue
- Asserts that a condition is true.
Example:
assertTrue(5 > 3);assertFalse
- Asserts that a condition is false.
Example:
assertFalse(5 < 3);assertThrows
- Asserts that a certain exception is thrown.
Example:
assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));assertNull, assertNotNull
- Asserts that an object is null or not null.
Example:
assertNull(user.getName());
assertNotNull(user.getId());4. Introduction to Mockito
What is Mockito?
Mockito is a mocking framework used to create mock objects in unit tests. Mocking allows you to test a class in isolation without depending on external resources like databases or web services.
Why use Mockito?
Mockito simplifies testing by:
- Replacing complex dependencies with mock objects.
- Ensuring that only the unit being tested is the focus.
- Allowing control over behavior of dependencies (e.g., simulating errors or delays).
5. Key Annotations in Mockito
5.1 @Mock
Use @Mock to create and inject a mock object in your test class. This is helpful when you want to isolate the class under test from its dependencies.
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.junit.jupiter.api.BeforeEach;
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);// Initialize mocks
}
}5.2 @InjectMocks
- Injects mocks into the object being tested.
- Useful when the class under test has dependencies (e.g., services that call repositories).
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.junit.jupiter.api.BeforeEach;
public class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;// Inject mocks into UserService
@BeforeEach
public void setUp() {
MockitoAnnotations.openMocks(this);
}
}5.3 @Spy
Partially mocks an object. It uses the real methods of the class unless mocked explicitly.
@Spy
private UserService userService;//Uses real methods unless explicitly mocked5.4 @Captor
Used to capture arguments passed to a method for further validation.
import org.mockito.Captor;
import org.mockito.ArgumentCaptor;
public class UserServiceTest {
@Captor
private ArgumentCaptor<Long> idCaptor;
@Test
public void testDeleteUser() {
userService.deleteUser(1L);
verify(userRepository).deleteById(idCaptor.capture());
assertEquals(1L, idCaptor.getValue());
}
}6. Mocking Behavior in Mockito
1. when-thenReturn: Mock Method Calls to Return Specific Values
The when-thenReturn method in Mockito is used to mock the return value of a method call. This is particularly useful when you want to simulate how a dependency behaves in a controlled manner without calling the actual method.
Syntax:
when(dependency.method()).thenReturn(mockedValue);- when(): Specifies the method call you want to mock.
- thenReturn(): Defines what value should be returned when the method is called.
Example:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
class UserServiceTest {
@Test
void testGetUser() {
UserService userService = mock(UserService.class); // Create a mock object of UserService
// Mock the behavior of getUserById method
when(userService.getUserById(1L)).thenReturn(new User(1L, "Amit", "[email protected]"));
// Test
User user = userService.getUserById(1L);
assertEquals("Amit", user.getName()); // Check if the mocked behavior is returned
}
}In this example:
- We mock the getUserById method of UserService to return a predefined User object when called with the argument 1L.
2. verify: Checking If a Method Was Called on a Mock
The verify method in Mockito is used to check whether a specific method was invoked on a mock object. It helps ensure that the correct methods are called during the execution of your tests.
Syntax:
verify(dependency).method();- verify(): Verifies that the specified method was called.
Example:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
class UserServiceTest {
@Test
void testDeleteUser() {
UserService userService = mock(UserService.class);//Create a mock object of UserService
// Call the deleteUser method
userService.deleteUser(1L);
// Verify if deleteUser method was called with argument 1L
verify(userService).deleteUser(1L);
}
}In this example:
- verify(userService).deleteUser(1L) checks if the deleteUser method was called with the argument 1L.
3. doThrow: Simulating Exceptions in Mocked Methods
The doThrow method is used when you want a mocked method to throw an exception. This is useful for testing how your code handles exceptions raised by dependencies.
Syntax:
doThrow(new Exception()).when(dependency).method();- doThrow(): Specifies the exception to be thrown.
- when(): Specifies the method that should throw the exception.
Example:
import static org.mockito.Mockito.*;
import org.junit.jupiter.api.Test;
class UserServiceTest {
@Test
void testDeleteUserThrowsException() {
UserService userService = mock(UserService.class); // Create a mock object of UserService
// Simulate an exception being thrown when deleteUser method is called
doThrow(new RuntimeException("User not found")).when(userService).deleteUser(1L);
// Act and assert the exception is thrown
assertThrows(RuntimeException.class, () -> userService.deleteUser(1L));
}
}In this example:
- doThrow(new RuntimeException(“User not found”)).when(userService).deleteUser(1L); simulates an exception when deleteUser is called with 1L.
- assertThrows(RuntimeException.class, () -> userService.deleteUser(1L)); ensures that the exception is actually thrown when the method is called.
7. @MockBean and @WebMvcTest
In Spring applications, unit testing controllers is a crucial part of verifying that the HTTP layer behaves as expected. For this, we use @WebMvcTest to test the controller layer in isolation and @MockBean to mock out the dependencies (like services or repositories) that the controller interacts with.
@WebMvcTest
- Purpose: It is used to test only the web layer (typically, controllers). It doesn’t load the entire application context but only the MVC components like controllers, @ControllerAdvice, and related configurations.
@MockBean
- Purpose: It allows us to mock dependencies in Spring’s application context. This is useful when testing controllers or services that rely on other beans (like repositories or services), allowing us to control the behavior of these mocks.
Example: Testing a UserController with @WebMvcTest and @MockBean
Controller Class: UserController
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/users")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/{id}")
public User getUserById(@PathVariable Long id) {
return userService.getUserById(id);
}
@PostMapping
public User createUser(@RequestBody User user) {
return userService.createUser(user.getName(), user.getEmail());
}
}In this UserController, we have two endpoints:
- GET/users/{id} to fetch a user by ID.
- POST/users to create a new user.
Service Class: UserService
import org.springframework.stereotype.Service;
@Service
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(Long id) {
return userRepository.findById(id);
}
public User createUser(String name, String email) {
return userRepository.save(new User(null, name, email));
}
}This UserService interacts with the UserRepository to fetch or save user data.
Repository Interface: UserRepository
public interface UserRepository {
User findById(Long id);
User save(User user);
}Test Class Using @WebMvcTest and @MockBean: UserControllerTest
To isolate the controller, we’ll mock out the UserService and test how the controller handles HTTP requests and responses.
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@WebMvcTest(UserController.class) //Only loads the web layer
class UserControllerTest {
@Autowired
private MockMvc mockMvc; //Allows us to perform HTTP requests in tests
@MockBean
private UserService userService; //Mocks the UserService
@Test
public void testGetUserById() throws Exception {
User mockUser = new User(1L, "Amit Singh", "[email protected]");
when(userService.getUserById(1L)).thenReturn(mockUser);
mockMvc.perform(get("/users/1"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Amit Singh"))
.andExpect(jsonPath("$.email").value("[email protected]"));
}
@Test
public void testCreateUser() throws Exception {
User mockUser = new User(1L, "Amit Singh", "[email protected]");
when(userService.createUser(anyString(), anyString())).thenReturn(mockUser);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\":\"Amit Singh\",\"email\":\"[email protected]\"}"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("Amit Singh"))
.andExpect(jsonPath("$.email").value("[email protected]"));
}
}Explanation of the Test Class:
@WebMvcTest
Loads only the web layer (the UserController in this case). Does not load the full application context, keeping the test lightweight and focused on the controller logic.
@MockBean
Mocks the UserService so that we can control the behavior of service calls within the controller. We use when(…).thenReturn(…) to define what the mock service should return when certain methods are called.
MockMvc
Allows us to simulate HTTP requests and verify the response. This helps to test the controller endpoints without needing to run the server.
Test Cases:
testGetUserById: Tests the GET /users/{id} endpoint. We mock the service to return a user, and verify that the controller responds with the correct user data in JSON format.
testCreateUser: Tests the POST /users endpoint. We mock the service to return a newly created user, and verify that the controller responds with the created user’s data.
Flow:
1. From Controller to Mocked Service The UserController exposes HTTP endpoints.
2. The UserService is injected into the controller using constructor injection.
3. In the test class, we mock the UserService using @MockBean.
4. MockMvc allows us to perform HTTP requests like GET and POST on the controller endpoints.
5. We define the behavior of the mocked UserService using Mockito’s when(…).thenReturn(…) to return predefined results.
6. We verify the response using jsonPath to check the JSON fields in the response body.
Running the Tests:
When you run these tests, Spring will:
- Spin up the UserController in a lightweight manner, excluding the full application context.
- Inject the mocked UserService into the controller.
- Simulate the HTTP requests using MockMvc and assert that the responses are as expected.
8. Complete example of a Spring Boot project using JUnit and Mockito
- Entity Class
package com.example.demo.entity;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
public User() {
}
public User(Long id, String name, String email) {
this.id = id;
this.name = name;
this.email = email;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}2. Repository Interface
package com.example.demo.repository;
import com.example.demo.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}3. Service Class
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.Optional;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
public Optional<User> getUserById(Long id) {
return userRepository.findById(id);
}
public User createUser(User user) {
return userRepository.save(user);
}
}4. Controller Class
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
@RestController
@RequestMapping("/users")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/{id}")
public ResponseEntity<User> getUserById(@PathVariable Long id) {
Optional<User> user = userService.getUserById(id);
return user.map(ResponseEntity::ok)
.orElse(new ResponseEntity<>(HttpStatus.NOT_FOUND));
}
@PostMapping
public ResponseEntity<User> createUser(@RequestBody User user) {
User createdUser = userService.createUser(user);
return new ResponseEntity<>(createdUser, HttpStatus.CREATED);
}
}5. Unit Test for Service Class
package com.example.demo.service;
import com.example.demo.entity.User;
import com.example.demo.repository.UserRepository;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.Optional;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.*;
class UserServiceTest {
@InjectMocks
private UserService userService;
@Mock
private UserRepository userRepository;
@BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
@Test
void getUserById() {
Long userId = 1L;
User mockUser = new User(userId, "John Doe", "[email protected]");
when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser));
Optional<User> user = userService.getUserById(userId);
assertEquals("John Doe", user.get().getName());
verify(userRepository, times(1)).findById(userId);
}
@Test
void createUser() {
User user = new User(null, "Jane Doe", "[email protected]");
User savedUser = new User(1L, "Jane Doe", "[email protected]");
when(userRepository.save(user)).thenReturn(savedUser);
User result = userService.createUser(user);
assertEquals(savedUser.getId(), result.getId());
verify(userRepository, times(1)).save(user);
}
}6. Unit Test for Controller Class (with @WebMvcTest and @MockBean)
package com.example.demo.controller;
import com.example.demo.entity.User;
import com.example.demo.service.UserService;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.http.MediaType;
import org.springframework.test.web.servlet.MockMvc;
import java.util.Optional;
import static org.mockito.Mockito.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@WebMvcTest(UserController.class)
class UserControllerTest {
@Autowired
private MockMvc mockMvc;
@MockBean
private UserService userService;
@Test
void testGetUserById() throws Exception {
Long userId = 1L;
User mockUser = new User(userId, "John Doe", "[email protected]");
when(userService.getUserById(userId)).thenReturn(Optional.of(mockUser));
mockMvc.perform(get("/users/{id}", userId))
.andExpect(status().isOk())
.andExpect(jsonPath("$.name").value("John Doe"))
.andExpect(jsonPath("$.email").value("[email protected]"));
verify(userService, times(1)).getUserById(userId);
}
@Test
void testCreateUser() throws Exception {
User newUser = new User(null, "Jane Doe", "[email protected]");
User createdUser = new User(1L, "Jane Doe", "[email protected]");
when(userService.createUser(Mockito.any(User.class))).thenReturn(createdUser);
mockMvc.perform(post("/users")
.contentType(MediaType.APPLICATION_JSON)
.content("{\"name\": \"Jane Doe\", \"email\": \"[email protected]\"}"))
.andExpect(status().isCreated())
.andExpect(jsonPath("$.name").value("Jane Doe"))
.andExpect(jsonPath("$.email").value("[email protected]"));
verify(userService, times(1)).createUser(any(User.class));
}
}7. Run Tests
- You can run these tests using any IDE like IntelliJ or Eclipse, or you can use Maven/Gradle:
mvn testExplanation of Components:
- Entity: Represents a user in the database with fields like id, name, and email.
- Repository: Provides methods to interact with the database for User objects.
- Service: Contains the business logic for fetching and creating users.
- Controller: Exposes API endpoints like GET/users/{id} and POST/users.
Explanation of Test Classes:
- UserServiceTest: Uses Mockito to mock the repository and tests the service logic.
- UserControllerTest: Uses @WebMvcTest to test the controller endpoints and @MockBean to mock the UserService.
Conclusion
Here, we explored the essential aspects of JUnit and Mockito, including key annotations, assertions, and practical examples for effective unit testing in Java. We demonstrated how to write tests that ensure code correctness, handle dependencies using mocks, and leverage various testing techniques to improve software quality. By incorporating JUnit and Mockito into your development workflow, you can catch bugs early, facilitate code refactoring, and maintain confidence in your codebase.
If you found this guide valuable, consider following me for more in-depth articles and tutorials on Java, testing frameworks, and other crucial development skills.
For those interested in connecting or seeking guidance, feel free to reach out to me on LinkedIn:Amit Singh. & Instagram: amit_singhh10.
If you enjoyed this post or found it helpful, don’t forget to clap!👏
Stackademic 🎓
Thank you for reading until the end. Before you go:
- Please consider clapping and following the writer! 👏
- Follow us X | LinkedIn | YouTube | Discord
- Visit our other platforms: In Plain English | CoFeed | Differ
- More content at Stackademic.com






