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