Let's Write Some Unit Tests
Create the Tests
Go to src/main/java/dev/pollito/users_manager/adapter/in/rest/UserController.java
, and do ctrl+intro
on the name of the class. On the pop-up menu, select Create test
.

In the next pop-up menu, leave everything as default.

Now you should have a new empty class created in src/test/java/dev/pollito/users_manager/adapter/in/rest/UserControllerTest.java
.
Repeat this process for as many classes as you want to test.
How to Test
There's not one true correct way of writing unit tests. Here is how I test classes based on their role.
Rest Controller Advice classes
These test classes should verify the exception handling. They check that:
- Each type of exception (
NoResourceFoundException
,NoSuchElementException
, etc.) is properly translated into appropriate HTTP status codes and problem details. - The problem details include required properties like timestamp and stack trace.
package dev.pollito.users_manager.config.advice;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.mock;
import static org.springframework.http.HttpStatus.*;
import java.util.NoSuchElementException;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.HttpStatus;
import org.springframework.http.ProblemDetail;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.servlet.resource.NoResourceFoundException;
@ExtendWith(MockitoExtension.class)
class ControllerAdviceTest {
@InjectMocks private ControllerAdvice controllerAdvice;
private static void problemDetailAssertions(
@NotNull ProblemDetail response, @NotNull Exception e, @NotNull HttpStatus httpStatus) {
assertEquals(httpStatus.value(), response.getStatus());
assertEquals(e.getClass().getSimpleName(), response.getTitle());
assertNotNull(response.getProperties());
assertNotNull(response.getProperties().get("timestamp"));
assertNotNull(response.getProperties().get("trace"));
}
@Test
void givenException_whenHandle_thenReturnsProblemDetail() {
Exception e = mock(Exception.class);
problemDetailAssertions(controllerAdvice.handle(e), e, INTERNAL_SERVER_ERROR);
}
@Test
void givenMethodArgumentTypeMismatchException_whenHandle_thenReturnsProblemDetail() {
MethodArgumentTypeMismatchException e = mock(MethodArgumentTypeMismatchException.class);
problemDetailAssertions(controllerAdvice.handle(e), e, BAD_REQUEST);
}
@Test
void givenNoResourceFoundException_whenHandle_thenReturnsProblemDetail() {
NoResourceFoundException e = mock(NoResourceFoundException.class);
problemDetailAssertions(controllerAdvice.handle(e), e, NOT_FOUND);
}
@Test
void givenNoSuchElementException_whenHandle_thenReturnsProblemDetail() {
NoSuchElementException e = mock(NoSuchElementException.class);
problemDetailAssertions(controllerAdvice.handle(e), e, NOT_FOUND);
}
}
Controller Classes
These test classes focus on the REST endpoints. They check response status codes and confirm responses are not null.
They usually:
- Mock service dependency injections to isolate the controller's responsibility.
- Use @Spy on mappers to use real mapping functionality while mocking other dependencies.
package dev.pollito.users_manager.adapter.in.rest;
import static java.util.Collections.emptyList;
import static org.junit.jupiter.api.Assertions.*;
import static org.mapstruct.factory.Mappers.getMapper;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.http.HttpStatus.OK;
import dev.pollito.users_manager.adapter.in.rest.dto.User;
import dev.pollito.users_manager.adapter.in.rest.mapper.UserMapper;
import dev.pollito.users_manager.domain.port.in.UserService;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.http.ResponseEntity;
@ExtendWith(MockitoExtension.class)
class UserControllerTest {
@InjectMocks private UserController userController;
@Mock private UserService userService;
@SuppressWarnings("unused")
@Spy
private UserMapper userMapper = getMapper(UserMapper.class);
@Test
void shouldReturnOk_whenFindAll() {
when(userService.findAll()).thenReturn(emptyList());
ResponseEntity<List<User>> response = userController.findAll();
assertEquals(OK, response.getStatusCode());
assertNotNull(response.getBody());
}
@Test
void shouldReturnOk_whenFindById() {
when(userService.findUserById(anyLong()))
.thenReturn(mock(dev.pollito.users_manager.domain.model.User.class));
ResponseEntity<User> response = userController.findById(1L);
assertEquals(OK, response.getStatusCode());
assertNotNull(response.getBody());
}
}
Service Implementation Classes
These test classes should be the most comprehensive ones because they verify your business logic.
- Classes that test the behavior of service implementations are the ones that will change more often.
Right now we have little logic, so the tests are basic.
package dev.pollito.users_manager.domain.service;
import static org.junit.jupiter.api.Assertions.*;
import org.apache.commons.lang3.NotImplementedException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.junit.jupiter.MockitoExtension;
@ExtendWith(MockitoExtension.class)
class UserServiceImplTest {
@InjectMocks private UserServiceImpl userService;
@Test
void shouldReturnUsersList_whenFindAll() {
assertNotNull(userService.findAll());
}
@Test
void shouldThrowNotImplementedException_whenFindUserById() {
assertThrows(NotImplementedException.class, () -> userService.findUserById(-1L));
}
}
Commit the progress so far.
git add .
git commit -m "unit tests"