TestContainers: tests with real dependencies

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

Article content


Common Use Cases

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.

To view or add a comment, sign in

More articles by Fabricio Soriano

  • Efficient Data Mapping Frameworks

    In application development, we often have to transfer data between different objects, especially when working with…

    4 Comments

Insights from the community

Others also viewed

Explore topics