Adopt modern DevOps practices

Oftentimes writing tests seem like just a nuisance you do after finishing a feature, before heading off to Confluence and JIRA to document your work. Sometimes (rarely, but still) you are right.

In a team of few co-located developers working on a tidy small to medium-sized project, verbosely organized (multiple testing environments, in-house QA team, etc.) you can easily get away without writing any tests and live to tell about it. Sure, you are probably aware of the fact that most of the time having meaningful unit tests would’ve made you faster long term, but you have real business value to add and it has to be finished by yesterday.

However, in a newly formed, constantly changing team of a few dozen people working on a massive project, located on 3 continents, spread across 12 time zones with different levels of experience and understanding of the task at hand yelling across the room, asking if someone knows should id be docID + “::” ID or just ID goes through the window. At that point, unit tests and integration tests become as necessary as working code itself, if not more.

In the pre-Docker era, you would usually add in-memory database such as H2, write mocks of other dependent services and call it a day. Thankfully today with Docker and Testcontainers things are much easier.

To quote authors of library themselves: “Testcontainers is a Java library that supports JUnit tests, providing lightweight, throwaway instances of common databases, Selenium web browsers, or anything else that can run in a Docker container.”

In order to start writing integration tests you just need to have Docker installed and write a few lines of code. Testcontainers takes care of container lifecycle, pulling images, waiting for container to be ready and much more. 

Case Study
As a case study I’ve chosen to demonstrate the use of Testcontainers for Couchbase integration testing. As in everything Java related, first we add dependency, in this case we are using Maven so we add:

Since I’m using Spring Boot and all accompanying autoconfigured Spring solutions (such as Spring Data) my goal is to integrate Testcontainers with Spring without losing any of Spring’s automagical stuff. To achieve this I’ve created abstract class AbstractCouchbaseTest that is in charge of instantiating Couchbase container and passing properties to Spring context. Full code of class is here:

Let’s dissect this code

App.class is bootstrap class of my app under test.
TestCouchbaseConfig.class is where Beans for test couchbase environment are defined, we’ll get back to it later.

Since I’m using Testcontainers to set up environment for Spring app it makes sense to dynamically initialize Spring context and that’s what CouchbaseInitializer class does. We could’ve used classpath resources to load properties from file, but I think this will suffice for the purpose of demo.

Next, the meat of these tests, @Testcontainers annotation.

This annotation enables Junit Jupiter integration with Testcontainers by scanning all fields in the annotated class for @Container annotation and then calls their container lifecycle methods. If the field is static, container will be reused for all tests in the class, while if the field is an instance field, container will be recreated for each test method in the class. That’s pretty much it for most of use cases. Rather simple, right?

After that, all that’s left is to configure CouchbaseEnvironment Bean to work with our containerized database. Since Testcontainers automatically finds free ports for our database we need to find mapped ports and configure CouchbaseEnvironment to use them. Here’s how:

Using this piece of code we’ve configured Spring Data Couchbase integration to use containerized Couchbase instance and we are ready to write integration tests. In each integration test you should extend AbstractCouchbaseTest class and then proceed to use Spring’s repositories and all other automagically configured stuff like you always did.

Conclusion

As software engineers, our main mission is to produce desired software at minimal cost possibleTechnologies such as Docker and Testcontainer significantly lower the amount of effort required to implement modern DevOps practices, increasing the quality of our products while lowering the cost of developing them, thereby making them a valuable addition to toolchain of each software engineer. For a bigger picture here is a great overview of DevOps know-how.

I hope this post will help you on your journey in adopting modern DevOps practices. Feel free to contact us at business@decode.agency or jobs@decode.agency if you want to become part of our team.

Get in touch
Is your company looking for a software development partner? Drop us a line at