CodeGym /Courses /Module 5. Spring /Lecture 158: Application testing: Unit and integration te...

Lecture 158: Application testing: Unit and integration tests

Module 5. Spring
Level 25 , Lesson 7
Available

We'll write two types of tests:

  1. Unit tests: they verify individual methods and components in isolation. No database or external services. Use mocks (Mockito) to simulate dependencies.
  2. Integration tests: they check how multiple components work together. For example, controllers talking to services and the database.

Getting ready to test

Testing dependencies

Spring Boot already includes what you need for basic testing. Still, make sure you have the following dependencies in your pom.xml:


<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.mockito</groupId>
    <artifactId>mockito-core</artifactId>
    <scope>test</scope>
</dependency>
<dependency>
    <groupId>org.testcontainers</groupId>
    <artifactId>testcontainers</artifactId>
    <scope>test</scope>
</dependency>

If you're using Gradle, the equivalent lines look like this:


testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.mockito:mockito-core'
testImplementation 'org.testcontainers:testcontainers'

After adding the dependencies, make sure the project reloads successfully.


Unit tests

Let's start by writing Unit tests for the service layer.

Code example: testing the service

Assume we have the following business logic in our service:


@Service
public class UserService {
    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User findUserById(Long id) {
        return userRepository.findById(id)
                .orElseThrow(() -> new UserNotFoundException("User not found with id: " + id));
    }
}

Testing with Mockito

To test this class we need to isolate it from the database. We do that with mocks:


@ExtendWith(MockitoExtension.class) // Hook up Mockito integration
class UserServiceTest {

    @Mock
    private UserRepository userRepository;

    @InjectMocks
    private UserService userService;

    @Test
    void shouldReturnUserWhenFound() {
        // Arrange
        Long userId = 1L;
        User mockUser = new User(userId, "John Doe", "john.doe@example.com");
        when(userRepository.findById(userId)).thenReturn(Optional.of(mockUser)); // Mock the repository behavior

        // Act
        User result = userService.findUserById(userId);

        // Assert
        assertNotNull(result);
        assertEquals("John Doe", result.getName());
        verify(userRepository, times(1)).findById(userId); // Verify that the method was called exactly once
    }

    @Test
    void shouldThrowExceptionWhenUserNotFound() {
        // Arrange
        Long userId = 999L;
        when(userRepository.findById(userId)).thenReturn(Optional.empty());

        // Act & Assert
        assertThrows(UserNotFoundException.class, () -> userService.findUserById(userId));
    }
}

What's happening here?

  1. We use the @Mock annotation to create a "fake" UserRepository.
  2. With @InjectMocks we inject that mock into UserService.
  3. In the tests we set up the repository behavior (when(...).thenReturn(...)) to create a controlled environment.
  4. We check that the method works correctly and throws an exception when the user isn't found.

Integration tests

Now let's move on to more complex cases where we need to verify component interaction. We'll use a real database (for example, H2) and test controllers and services together.

Setting up integration tests

For integration testing controllers, Spring Boot gives you a handy tool: @SpringBootTest. It boots up the application context so you can test layer interactions.

Example REST controller under test:


@RestController
@RequestMapping("/api/users")
public class UserController {
    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUserById(@PathVariable Long id) {
        User user = userService.findUserById(id);
        return ResponseEntity.ok(user);
    }
}

Integration test for the controller

Let's write a test that hits our API directly:


@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // Start with a web server
@AutoConfigureMockMvc // Configure MockMvc
class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    @Test
    void shouldReturnUserWhenExists() throws Exception {
        mockMvc.perform(get("/api/users/1"))
               .andExpect(status().isOk())
               .andExpect(jsonPath("$.name").value("John Doe"));
    }

    @Test
    void shouldReturnNotFoundWhenUserDoesNotExist() throws Exception {
        mockMvc.perform(get("/api/users/999"))
               .andExpect(status().isNotFound());
    }
}

Debugging tests and common mistakes

  1. Context configuration error: make sure your @SpringBootTest configs and mocks are set up correctly.
  2. Lazy loading of data: sometimes integration tests fail because entity relationships are lazily loaded. Fix this by forcing explicit loading (FetchType.EAGER) or using DTOs.
  3. Slow tests: if your tests are slow, consider using Testcontainers to isolate tests in containers locally.

Automating tests

Hook tests into your CI/CD pipeline. On every commit your Unit and integration tests should run to prevent bugs from getting into the main branch. Most tools (Jenkins, GitLab CI, GitHub Actions) integrate easily with Maven or Gradle.


# Example GitLab CI pipeline
stages:
  - test

test:
  script:
    - ./mvnw test

This is how you can gain confidence in your app before deploying it. Now you know how to write both Unit and integration tests. So, ready for the next step? Time to dive into containerization and build automation! 🚀

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION