What is Testcontainers, and why should you use it? (original) (raw)

Modern software systems tackle complex business problems by leveraging various technologies and tools. Nowadays, hardly any software system works in isolation; they usually talk to databases, messaging systems, and cache providers and interact with many other 3rd party services. In today’s highly competitive market, time-to-market is crucial. Businesses want to put their product on the market as soon as possible, get feedback and iterate on it. To achieve that aspect of agility, one should have a solid Continuous Integration and Continuous Deployment (CI/CD) process. A crucial part of the CI/CD process is automated testing to ensure the correctness of the application behavior.

While Unit Testing helps in testing the business logic and implementation details by isolating from external services like databases, messaging systems etc., the bulk of the application code might still be in integrating with those external services. To be fully confident with our application, we should write integration tests along with unit tests to ensure that our application is fully functional.

Historically Integration testing is considered difficult because of the challenges in maintaining an "integration testing environment". Integration testing with pre-provisioned infrastructure is challenging because of the following reasons:

Due to the challenges mentioned above, some people lean towards using services with in-memory or embedded variations of the required services for integration testing. For example, if an application uses the Postgres database, then H2 in-memory database is used as a substitute for testing. While this is an improvement over not writing integration tests at all, using mocks or in-memory versions of those services brings its own problems:

Now, welcome to the wonderful world of Testcontainers, where integration testing with real services is not only possible but also as easy as writing unit tests 🙂

Testcontainers is a testing library that provides easy and lightweight APIs for bootstrapping integration tests with real services wrapped in Docker containers. Using Testcontainers, you can write tests talking to the same type of services you use in production without mocks or in-memory services.

A typical Testcontainers-based integration test works as follows:

testcontainers lifecycle simple 2

The only requirement to run Testcontainers-based tests is to have a Docker-API compatible container runtime. If you have Docker Desktop installed and running, you are good to go. For more information on Docker environments supported by Testcontainers refer to https://www.testcontainers.org/supported_docker_environment/.

What problems does Testcontainers solve?

Testcontainers solves the integration testing problems mentioned above by enabling us to test our application using real services and thereby increasing the confidence level on our code changes.

testcontainers dev ci

By using Testcontainers:

You can use Testcontainers with many popular programming languages, including Java, .NET, Go, NodeJS, Rust, and Python, and more language support on its way.

Conclusion

We have explored the challenges with integration testing and understood why testing with mocks or in-memory services is not always a good idea. Then we talked about how Testcontainers solves the integration testing problem and enables us to test with real services.

Further Reading