TestContainers: tests with real dependencies
Software testing is an essential component in developing robust and reliable applications. As software architectures become more complex, especially with the adoption of microservices and the need for continuous integration, the challenges associated with testing also increase. In this context, TestContainers emerges as a powerful tool, offering a more realistic and controlled testing environment.
What is TestContainers?
Testcontainers is an open-source framework for providing throwaway, lightweight instances of databases, message brokers, web browsers, or just about anything that can run in a Docker container.
Key Advantages
Isolation and Reproducibility
One of the great advantages of TestContainers is the ability to isolate tests in Docker containers. This means that each test can be run in a clean and consistent environment, eliminating external interference and ensuring reproducibility.
Simplified Configuration
TestContainers simplifies the configuration of complex testing environments. Instead of manually setting up databases or other necessary services, developers can define Docker containers directly in the test code using an intuitive API. This reduces configuration overhead and allows tests to be executed more efficiently.
Integration with Popular Tools
The library is compatible with various popular technologies such as PostgreSQL, MySQL, MongoDB, Kafka, RabbitMQ, Selenium, and even testing frameworks like JUnit and TestNG. This broadens the range of possibilities for developers, who can test integration with different services and technologies in a simplified manner.
Support for many languages
Support for many languages and testing frameworks, all you need is Docker
Common Use Cases
Recommended by LinkedIn
Integration Tests
Integration tests are one of the primary use cases for TestContainers. The ability to start containers for databases, queue services, and other components makes TestContainers ideal for verifying the interaction between different parts of a system.
End-to-End (E2E) Tests
In end-to-end tests, it is crucial to test the system as a whole. Using containers for external services and browsers, TestContainers enables the creation of realistic test scenarios that replicate end-user behavior.
Development Environments
Developers can use TestContainers to create consistent and isolated development environments. This is especially useful in teams where different members may have different environment configurations.
Example
This example is available in my github (testcontainer).
Let's say we have an API to retrieve items and we would like to execute some tests on this API, like: calling the API to retrieve the items and check if the response is correct.
Controller
=========
@GetMapping
public List<Item> getAllItems() {
return itemService.getAllItems();
}
Service
=========
public List<Item> getAllItems() {
return itemRepository.findAll();
}
Repository
=========
public interface ItemRepository extends MongoRepository<Item, String> {
}
We can create a class that will set all we need to run our IT tests, so we can use it in all classes.
@SpringBootTest
@AutoConfigureMockMvc
@Testcontainers
public abstract class AbstractIntegrationTest {
@Container
public static GenericContainer mongoDBContainer = new GenericContainer("mongo:latest")
.withExposedPorts(27017)
.withReuse(false);
static MongoClient mongoClient = MongoClients.create();
@BeforeAll
public static void setupDatabase() {
MongoDatabase database = mongoClient.getDatabase("testcontainers");
MongoCollection<Document> collection = database.getCollection("items");
collection.drop();
// Insert sample data
Document item1 = new Document("name", "Item 1").append("price", 10.0);
Document item2 = new Document("name", "Item 2").append("price", 20.0);
collection.insertMany(List.of(item1, item2));
}
}
Finally, we can create our IT that will get items from the DB without mocking any data.
public class ItemControllerIT extends AbstractIntegrationTest {
@Autowired
private MockMvc mockMvc;
@Autowired
private ObjectMapper objectMapper;
@Test
public void testGetAllItems() throws Exception {
// WHEN
List<Item> items = objectMapper.readValue(
mockMvc.perform(MockMvcRequestBuilders.get("/api/items"))
.andExpect(MockMvcResultMatchers.status().isOk())
.andReturn()
.getResponse()
.getContentAsString(),
new TypeReference<>() {
});
// THEN
assertEquals(2, items.size());
assertEquals("Item 1", items.get(0).name());
assertEquals(10.0, items.get(0).price());
assertEquals("Item 2", items.get(1).name());
assertEquals(20.0, items.get(1).price());
}
}
Conclusion
TestContainers stands out as an essential tool for modern software testing, providing isolated, configurable, and reproducible testing environments. Its integration with various technologies and ease of use allow development teams to ensure the quality and reliability of their applications efficiently. With the continuous growth in the complexity of software systems, tools like TestContainers are vital for maintaining an agile and secure workflow.