Kube Cloud Pt2 | Automated Testing with TestContainers

Kube Cloud Pt2 | Automated Testing with TestContainers

Kubernetes Application Hosted in the Cloud (Part 2)

full course
  1. Cloud Kube Pt2 | Setting Up a Datastore
  2. Kube Cloud Pt2 | Automated Testing with TestContainers
  3. Kube Cloud Pt2 | Database Configuration in Kubernetes

In the last section we implemented service endpoint that stored data in a mongodb backend. In this session we’re going to build a component test to automatically verify that functionality.

TestContainer Overview

TestContainers allow us to mock external resources with a docker based implementation. This is similar to “regular” mocking (with, for example, embedded databases) in that we can run our tests within our build environment and get immediate feedback. However, TestContainers use actual implementations of resources running within docker containers which offers higher fidelity to the actual implementation that we’ll be deploying against. This does come at some cost though, since these are docker images, we’ll have a dependency on an external resource (the docker registry and the images it hosts) within our build. While not normally a problem, keep in mind that network issues, rate limiting of docker hosted registries, etc… could cause your build. We normally want to avoid externalities in our build processes.

Implementing the TestContainer

Add these dependencies to your pom.xml

...
    <properties>
...
        <javafaker.version>0.15</javafaker.version>
...
    </properties>
...
        <dependency>
            <groupId>org.testcontainers</groupId>
            <artifactId>mongodb</artifactId>
            <version>1.16.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.github.javafaker</groupId>
            <artifactId>javafaker</artifactId>
            <version>${javafaker.version}</version>
        </dependency>

First, I’m going to modify a few thing. First, add a builder to the CreateMessageRequestDTO object so I can build test data easier:

package com.bullyrooks.cloud_application.controller.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CreateMessageRequestDTO {
    private String firstName;
    private String lastName;
    private String message;
}

Then I’m going to update CreateMessageResponseDTO so that I can return the messageId, so I can run my verifications

package com.bullyrooks.cloud_application.controller.dto;

import lombok.Data;

@Data
public class CreateMessageResponseDTO {
    private String messageId;
    private String firstName;
    private String lastName;
    private String message;
}

Create a new class called MessageControllerTest in the test path under the com.bullyrooks.cloud_application.controller package. Add this code

package com.bullyrooks.cloud_application.controller;

import com.bullyrooks.cloud_application.controller.dto.CreateMessageRequestDTO;
import com.bullyrooks.cloud_application.controller.dto.CreateMessageResponseDTO;
import com.bullyrooks.cloud_application.repository.MessageRepository;
import com.bullyrooks.cloud_application.repository.document.MessageDocument;
import com.github.javafaker.Faker;
import com.github.javafaker.service.FakeValuesService;
import com.github.javafaker.service.RandomService;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.DynamicPropertyRegistry;
import org.springframework.test.context.DynamicPropertySource;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.testcontainers.containers.MongoDBContainer;

import java.util.List;
import java.util.Locale;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Slf4j
@AutoConfigureMockMvc
@ActiveProfiles("test")
public class MessageControllerTest {

static MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:4.4.10");
{
mongoDBContainer.start();
}
@DynamicPropertySource
static void setProperties(DynamicPropertyRegistry registry) {
registry.add("spring.data.mongodb.uri", mongoDBContainer::getReplicaSetUrl);
}
private final String MESSAGE_PATH = "/message";

@LocalServerPort
int randomServerPort;

@Autowired
MessageRepository messageRepository;

FakeValuesService fakesvc = new FakeValuesService(
new Locale("en-US"), new RandomService());
Faker faker = new Faker();


@Test
void testGetOpportunitiesForNotification(){
Long userId = 1l;

//given
CreateMessageRequestDTO request = CreateMessageRequestDTO
.builder()
.firstName(faker.name().firstName())
.lastName(faker.name().lastName())
.message(faker.gameOfThrones().quote())
.build();

//when
RestTemplate restTemplate = new RestTemplate();
String baseUrl = "http://localhost:" + randomServerPort + MESSAGE_PATH;
UriComponents builder = UriComponentsBuilder.fromHttpUrl(baseUrl)
.build();

ResponseEntity<CreateMessageResponseDTO> result = restTemplate.postForEntity(
builder.toUri(), request, CreateMessageResponseDTO.class);

//then
CreateMessageResponseDTO dto = result.getBody();
assertEquals(request.getFirstName(), dto.getFirstName());
assertEquals(request.getLastName(), dto.getLastName());
assertEquals(request.getMessage(), dto.getMessage());

MessageDocument savedDoc = messageRepository.findById(dto.getMessageId()).get();
assertEquals(request.getFirstName(), savedDoc.getFirstName());
assertEquals(request.getLastName(), savedDoc.getLastName());
assertEquals(request.getMessage(), savedDoc.getMessage());
}

}

Let me explain what’s going on here. First, I’m telling maven (and spring) that I have a dependency on a docker image that will allow me to mock MongoDB. I’m also telling spring boot test to stand that image up in this test and wherever I reference my mongodb uri, use the docker instance (via spring.data.mongodb.uri )

Next, I’m standing up an instance of faker, which will allow me to mock up test data in a random way, so that test data should never collide.

Then I’m making a request against my controller endpoint, getting the response and verifying the response matches the request.

Finally, I’m going to the database instance (the docker image) and verifying the state of the database for my messageId. This confirms that the record was ‘written’.

Run this test and it should pass.

Git Commit and Push

$ git add .
warning: LF will be replaced by CRLF in pom.xml.
The file will have its original line endings in your working directory

$ git commit -m "new component test"
[database 813361e] new component test
 4 files changed, 119 insertions(+)
 create mode 100644 src/test/java/com/bullyrooks/cloud_application/controller/MessageControllerTest.java

$ git push
Enumerating objects: 35, done.
Counting objects: 100% (35/35), done.
Delta compression using up to 4 threads
Compressing objects: 100% (13/13), done.
Writing objects: 100% (20/20), 2.76 KiB | 706.00 KiB/s, done.
Total 20 (delta 5), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (5/5), completed with 5 local objects.
To github.com-bullyrook:bullyrooks/cloud_application.git
   f6b7f59..813361e  database -> database

0 comments on “Kube Cloud Pt2 | Automated Testing with TestContainersAdd yours →

Leave a Reply

Your email address will not be published. Required fields are marked *