Integration Testing Strategies for Spring Web and Data

Date: December 8, 2024


Integration testing is crucial for ensuring your Spring Boot application’s different layers work seamlessly together — especially the web (REST controllers) and data (repositories, databases) layers. Unlike unit tests, integration tests load parts of the Spring context and often interact with real or in-memory databases to validate your app behavior end-to-end.

In this post, we’ll explore strategies and best practices to write effective integration tests for Spring Web and Spring Data.


Why Integration Tests?

  • Verify that web endpoints, service layers, and persistence layers collaborate correctly.
  • Catch issues missed by isolated unit tests (e.g., incorrect query, transaction problems).
  • Provide confidence before deploying to production.

Tooling Overview

Spring Boot provides powerful support for integration testing via:

  • @SpringBootTest: Loads full Spring context.
  • MockMvc: Simulates HTTP requests for testing controllers.
  • @DataJpaTest: Loads only JPA-related components, ideal for repository testing.
  • Testcontainers: Run lightweight, throwaway Docker containers (e.g., PostgreSQL) for real database tests.
  • @Transactional: Roll back transactions after each test to keep DB clean.

Strategy 1: Testing Controllers with MockMvc and In-Memory DB

Example: Test a REST API backed by H2 in-memory database.

@SpringBootTest
@AutoConfigureMockMvc
@TestPropertySource(locations = "classpath:application-test.properties")
@Transactional
public class UserControllerIntegrationTest {

  @Autowired
  private MockMvc mockMvc;

  @Autowired
  private UserRepository userRepository;

  @BeforeEach
  void setup() {
    userRepository.save(new User(null, "Bob"));
  }

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

Notes:

  • @Transactional rolls back DB changes after each test.
  • application-test.properties configures the app to use H2 database.
  • MockMvc performs requests without running a real HTTP server.

Strategy 2: Isolating Repository Tests with @DataJpaTest

For repository testing focusing on JPA logic:

@DataJpaTest
public class UserRepositoryTest {

  @Autowired
  private UserRepository userRepository;

  @Test
  void testFindByName() {
    userRepository.save(new User(null, "Alice"));
    Optional<User> user = userRepository.findByName("Alice");

    Assertions.assertTrue(user.isPresent());
    Assertions.assertEquals("Alice", user.get().getName());
  }
}

This annotation configures an embedded database, scans for JPA entities, and sets up Spring Data repositories for fast, focused tests.


Strategy 3: Using Testcontainers for Realistic DB Testing

While in-memory DBs are fast, sometimes you want to test with your actual database engine.

@Testcontainers
@SpringBootTest
public class UserRepositoryTest {

  @Container
  public static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15")
    .withDatabaseName("testdb")
    .withUsername("test")
    .withPassword("test");

  @Autowired
  private UserRepository userRepository;

  @DynamicPropertySource
  static void properties(DynamicPropertyRegistry registry) {
    registry.add("spring.datasource.url", postgres::getJdbcUrl);
    registry.add("spring.datasource.username", postgres::getUsername);
    registry.add("spring.datasource.password", postgres::getPassword);
  }

  @Test
  void testFindAllUsers() {
    userRepository.save(new User(null, "Eve"));
    List<User> users = userRepository.findAll();
    Assertions.assertFalse(users.isEmpty());
  }
}

Advantages:

  • Runs your tests against a real PostgreSQL container.
  • Tests queries and DB-specific features exactly as in production.
  • Works well in CI pipelines.

Strategy 4: Full End-to-End Tests with WebTestClient

For reactive apps or to test actual HTTP endpoints, use WebTestClient:

@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class ReactiveUserControllerTest {

  @Autowired
  private WebTestClient webTestClient;

  @Test
  void testGetUsers() {
    webTestClient.get().uri("/users")
      .exchange()
      .expectStatus().isOk()
      .expectBodyList(User.class)
      .hasSizeGreaterThan(0);
  }
}

Best Practices Summary

  • Use in-memory databases for fast feedback in most tests.
  • Apply Testcontainers for realistic DB integration in critical paths.
  • Use @Transactional to keep tests isolated.
  • Mock external services when needed with tools like WireMock.
  • Combine unit tests (fast, isolated) with integration tests (context and real DB).
  • Run integration tests regularly in your CI pipeline.

Conclusion

Integration testing with Spring Web and Data is essential for delivering reliable apps. By strategically choosing testing scope, tools, and data sources, you can catch issues early and maintain high quality.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *