Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Integration Tests and Your Couchbase Application

DZone's Guide to

Integration Tests and Your Couchbase Application

You can use Couchbase's TestContainers to make sure the pieces of your app, including the frontend, the backend, and the containers among them, talk to each other.

· Database Zone
Free Resource

Traditional relational databases weren’t designed for today’s customers. Learn about the world’s first NoSQL Engagement Database purpose-built for the new era of customer experience.

Proper integration tests require a complete setup of your infrastructure. And this can be a little hard to put in place sometimes, especially when you need to support your developer laptop, your CI node, or any other machine where you need to run your tests. A good solution to fix this is to use containers as a common runtime. Docker works the same on every machine. So as long as it is installed on your developer laptop and your CI node, you are fine. So, yet again, I’ll show you how to do this using TestContainers.

What We Want to Test

There is a great sample app to test Couchbase with the Travel Sample. The Java backend is located here https://github.com/couchbaselabs/try-cb-java/ and the frontend here https://github.com/couchbaselabs/try-cb-frontend/. It will allow you to get an understanding of various features like N1QL, FTS, or subdocuments. It’s a great place to start learning Couchbase. And while it’s a great place to learn, we still don’t have any integration tests for it. So I started out a new project to remedy that. It’s available on https://github.com/couchbaselabs/try-cb-integration-test. This is what I will write about today.

Test Setup

To test the frontend, I am going to use Selenium. It allows you to basically specify where you want to click on a page, enter keystrokes, and verify the content of a page. So you can tell the selenium driver to load a page at a particular URL, verify that it’s correctly loaded, click on a link and verify that the new page brought you where you wanted. That’s all we are going to do today as far as testing go. The important thing here is how to setup this. First, take a look at my Test code:

    @Test
    public void testTab() throws InterruptedException {
        RemoteWebDriver driver = chrome.getWebDriver();
        driver.get("http://trycbfront");
        WebElement usernameField = (new WebDriverWait(driver, 10))
                .until(ExpectedConditions.presenceOfElementLocated(By.name("username")));

        // SIGNUP
        usernameField.sendKeys("ld@cb.com");
        driver.findElementByName("password").sendKeys("password");
        driver.findElementByTagName("button").click();

        // Verify SIGNUP
        String textElement = (new WebDriverWait(driver, 10))
                .until(ExpectedConditions.presenceOfElementLocated(By.xpath(("/html/body/app-root/div/div[2]/div[1]/div/div[2]/app-home/div[1]/div/div[1]/div[1]/div/div/div[2]/div/small/strong")))).getText();
        Assert.assertEquals("Find a Flight", textElement);

        // Test Cart Page
        navigateToCart(driver);
        textElement = (new WebDriverWait(driver, 10))
                .until(ExpectedConditions.presenceOfElementLocated(By.xpath(("/html/body/app-root/div/div[2]/div[1]/div/div[2]/app-cart/div[1]/button")))).getText();
        Assert.assertEquals("Clear Cart", textElement);
    }

    public void navigateToCart(RemoteWebDriver driver) {
        driver.findElementByXPath("/html/body/app-root/div/div[2]/div[1]/div/div[1]/div/app-navbar/div/div[3]/div/a[2]").click();
}


If you are familiar with Selenium, your eyes are probably hurting by looking at those big, long XPath values. In most applications, this will be replaced by the id of the DOM element you are looking for. Now that we got this out of the way, take a look at the first line: RemoteWebDriver driver = chrome.getWebDriver();

The Selenium driver was given by a Chrome object. This object represents a container running selenium. It’s possible thanks to TestContainers and their Selenium integration. But as it’s a container, to access something else, you either build it in the container or you have to link to another container. Which is of course what has been done. To run the Travel Sample, you need three things. You need a Couchbase Server, a Travel Sample backend, and a Travel Sample frontend, preferably all in their own container. Fortunately, we have a Docker image for each of those. Please refer to the project Readme if you want to build them.

For all these to work, you need to start them in a particular order. First start Couchabse, because it’s needed by the backend. Then, start the backend because it is needed by the frontend, then the frontend because it’s needed by the Selenium image containing Selenium and a browser. So we need to make sure each container is started and ready before launching the other one. I say ready because it can be different than just started in some cases. When you start a Couchbase container, you then have to configure it. As for the Spring Boot application for the backend, it takes some time to be up and ready to accept connections. Fortunately, TestContainers has thought about this and provides some interesting waiting mechanism.

Let’s start with the Couchbase Container:

    public static final String clusterUser = "Administrator";
    public static final String clusterPassword = "password";

    public static CouchbaseContainer couchbaseContainer = new CouchbaseContainer()
            .withFTS(true)
            .withIndex(true)
            .withQuery(true)
            .withTravelSample(true)
            .withClusterUsername(clusterUser)
            .withClusterPassword(clusterPassword)
            .withNewBucket(DefaultBucketSettings.builder().enableFlush(true).name("default").quota(100).replicas(0).type(BucketType.COUCHBASE).build());

    static {
        couchbaseContainer.start();
    }


You can see that it can be easily configured with this fluent API. Here, I am setting this up so that it will have the FTS, Index, and Query services activated, that the travel sample will be preloaded, with my own username and passsword, and with a default bucket as well.

Usually, with TestContainers, you would have a @ClassRule in your tests. That would start the container automatically. But it would start everything with a @ClassRule at the same time. Unfortunately, we can’t do that because, as explained earlier, containers need to be up and ready before starting the next one.

So we can hack our way through this by adding a static block of code. They are all executed in order. So if we start our container in a start block, we can make sure they are up and ready before the next static block. The CouchbaseContainer Waiting strategy makes polls on Couchbase’s REST API status and makes sure the node state is healthy when considering whether it's ready so it can stop waiting. If you want more details about CouchbaseContainer, you can look at the code here.

The next step is to start the Java backend, since Couchbase is up and ready:

    public static GenericContainer trycbBack = new LinkedContainer("trycb/java:latest")
            .withLinkToContainer(couchbaseContainer, "couchbase")
            .withExposedPorts(8080)
            .withCommand("-Dspring.couchbase.bootstrap-hosts="+couchbaseContainer.getContainerIpAddress())
            .waitingFor(new HttpWaitStrategy().forPath("/wut").forStatusCode(404));

    static {
        trycbBack.start();
    }


Some interesting things to note here: Again, the presence of a static block that starts the container for the same reasons stated previously. You can see we are exposing port 8080 here. No need to do that with CouchbaseContainer, as it has a specific class dedicated to the default Couchbase image.

As such, it has a default configuration for ports as well as a predefined waiting strategy. For the backend, we use a GenericContainer, so we need to specify those. The waiting strategy will wait until getting a 404 for the configured path. If it answers something, then basically, the server is up and the frontend can use it. You will also see that the Java parameter defining Couchbase’s address is given and the value is fetched from the CouchbaseContainer instance.

The last bit of very useful information is the call to the withLinkToContainer method. It’s not part of the GenericContainer API. Not sure why. I had to extend it:

public class LinkedContainer<SELF extends LinkedContainer<SELF>> extends GenericContainer<SELF> {

    public LinkedContainer(String name) {
        super(name);
    }

    public SELF withLinkToContainer(LinkableContainer otherContainer, String alias) {
        addLink(otherContainer, alias);
        return self();
}


It simply makes sure there will be a Docker link between CouchbaseContainer and the backend. This way, you can make sure containers can talk to each other.

Now that Couchbase and the backend are up and ready, we can start the frontend. It’s an Angular2 application inside the default Nginx container. It needs to access the backend, so we need to add a link to the previous container. Something you need to make sure of is being able to change the URL of the backend server dynamically in your Angular app. This can be done easily with Environments. This test image has been built with the test environment and, as such, look for the trycbBack hostname, which is the name give in the withLinkToContainer method. And again, we start the container in a static block.

    public static GenericContainer trycbFront = new LinkedContainer("trycb/front:latest").withLinkToContainer(trycbBack, "trycbBack").withExposedPorts(80);

    static {
        trycbFront.start();
    }


Once all those containers are up and running, we can start the Selenium container. This one is part of the TestContainers project and gives you access to several features. You can decide if you want to use Chrome or Firefox and if you want the test to be recorded. In our case, it’s a Chrome driver, and the test will be recorded and stored in the target folder. You will also notice that we add a link to both the frontend and backend. The frontend is accessed in the Chrome browser inside the container, and the Angular app will access the backend from it. So, you need to make sure that it’s accessible as well from the Selenium container and that your CORS configuration will allow communication.

    @ClassRule
    public static BrowserWebDriverContainer chrome = new BrowserWebDriverContainer()
            .withLinkToContainer(trycbFront, "trycbfront")
            .withLinkToContainer(trycbBack, "trycbback")
            .withDesiredCapabilities(DesiredCapabilities.chrome())
            .withRecordingMode(BrowserWebDriverContainer.VncRecordingMode.RECORD_ALL, new File("target"));


Now you have everything setup and you can run the test presented at the beginning of this post. I hope this was useful and that you will now use TestContainers for easy integration tests of Couchbase-based applications.

Learn how the world’s first NoSQL Engagement Database delivers unparalleled performance at any scale for customer experience innovation that never ends.

Topics:
database ,tutorial ,integration testing ,couchbase ,testcontainers ,selenium

Published at DZone with permission of Laurent Doguin, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}