Labs ICT
โญ Pro Login

Integration Testing with JUnit

Unit tests are great, but sometimes you need to verify that multiple components work together correctly. That is where integration testing comes in. Integration tests check the boundaries between components โ€” does the service layer correctly talk to the repository? Does the API endpoint correctly process requests?

Unit Tests vs Integration Tests


+------------------+---------------------+-------------------------+
|                  |  Unit Test          |  Integration Test       |
+------------------+---------------------+-------------------------+
|  Scope           |  Single class       |  Multiple components    |
|  Speed           |  Milliseconds       |  Seconds to minutes     |
|  Dependencies    |  Mocked             |  Real                   |
|  Isolation       |  Fully isolated     |  Tests interaction      |
|  Failure         |  Points to exact    |  May be harder to       |
|                  |  code               |  pinpoint               |
+------------------+---------------------+-------------------------+
    

Database Integration Test


import org.junit.jupiter.api.*;
import org.junit.jupiter.api.Tag;
import java.sql.*;

@Tag("integration")
@Tag("database")
class UserRepositoryIntegrationTest {

    private Connection connection;
    private UserRepository repository;

    @BeforeAll
    static void setUpDatabase() throws Exception {
        // Using H2 in-memory database for tests
        Connection conn = DriverManager.getConnection(
            "jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1"
        );
        conn.createStatement().execute("""
            CREATE TABLE users (
                id INT PRIMARY KEY AUTO_INCREMENT,
                name VARCHAR(100),
                email VARCHAR(100)
            )
        """);
    }

    @BeforeEach
    void setUp() throws Exception {
        connection = DriverManager.getConnection(
            "jdbc:h2:mem:testdb"
        );
        repository = new UserRepository(connection);
    }

    @AfterEach
    void tearDown() throws Exception {
        connection.close();
    }

    @Test
    void shouldSaveAndRetrieveUser() {
        User user = new User("John", "john@example.com");
        repository.save(user);

        User found = repository.findByEmail("john@example.com");

        assertNotNull(found);
        assertEquals("John", found.getName());
        assertEquals("john@example.com", found.getEmail());
    }

    @Test
    void shouldReturnNullForNonexistentUser() {
        User found = repository.findByEmail("nobody@example.com");
        assertNull(found);
    }

    @Test
    void shouldUpdateUser() {
        User user = new User("John", "john@example.com");
        repository.save(user);

        user.setName("Jane");
        repository.update(user);

        User found = repository.findByEmail("john@example.com");
        assertEquals("Jane", found.getName());
    }
}
    

REST API Integration Test


@Tag("integration")
@Tag("api")
class UserControllerIntegrationTest {

    private MockMvc mockMvc;

    @BeforeEach
    void setUp() {
        mockMvc = MockMvcBuilders
            .standaloneSetup(new UserController(new UserService()))
            .build();
    }

    @Test
    void shouldGetUserById() throws Exception {
        mockMvc.perform(get("/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John"))
            .andExpect(jsonPath("$.email").value("john@example.com"));
    }

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

    @Test
    void shouldCreateUser() throws Exception {
        String json = """
            {"name": "Jane", "email": "jane@example.com"}
        """;

        mockMvc.perform(post("/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content(json))
            .andExpect(status().isCreated())
            .andExpect(jsonPath("$.name").value("Jane"));
    }
}
    

@SpringBootTest (Spring Boot)


import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.beans.factory.annotation.Autowired;

@SpringBootTest
class ApplicationIntegrationTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoads() {
        assertNotNull(context);
    }

    @Test
    void allBeansCreated() {
        assertTrue(context.containsBean("userService"));
        assertTrue(context.containsBean("userRepository"));
    }
}
    

Test Containers

For real databases and services, use Testcontainers:


import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.containers.MySQLContainer;

@Testcontainers
class MySQLIntegrationTest {

    @Container
    static MySQLContainer<?> mysql = new MySQLContainer<>("mysql:8.0")
        .withDatabaseName("testdb")
        .withUsername("test")
        .withPassword("test");

    @Test
    void shouldConnectToRealDatabase() {
        Connection conn = DriverManager.getConnection(
            mysql.getJdbcUrl(),
            mysql.getUsername(),
            mysql.getPassword()
        );
        // Test with real MySQL
        assertNotNull(conn);
    }
}
    

When to Write Integration Tests

  • Database access layers (repositories, DAOs)
  • REST API endpoints
  • Message queue producers and consumers
  • Authentication and authorization flows
  • External service integrations
  • Complex business workflows that span multiple services

๐Ÿงช Quick Quiz

In integration testing with JUnit, what are you typically testing?