In this blog, we’ll explore how to set up containerized API testing using Testcontainers and REST Assured. We will walk through the entire process, including installing Docker, creating a mock server container, and writing test cases in Java.
What is RestAssured?
RestAssured is a Java library specifically designed for testing and validating RESTful APIs. It simplifies the process of making HTTP requests and checking the responses in an intuitive, readable way. Here’s a quick overview of what it does and how it’s typically used:
How RestAssured is Typically Used
- Setting Up Requests: Configure details such as base URI, headers, and authentication.
- Sending Requests: Use methods like
.get(),.post(), etc., to send requests to the API. - Validating Responses: Check status codes, headers, and contents of the response body using fluent validation methods.
What are Test Containers?
Testcontainers is a Java library for running Docker containers in integration tests, enabling realistic, isolated testing environments. It lets you spin up temporary instances of databases, web servers, or message brokers right from your test code. Each container is automatically started and stopped, so tests run in clean environments every time.
What is Docker and Why Do We Containerize?
Docker is a platform design and an open- source that allows automating different things like deployment, scaling, and management of applications using containerization. It provides an additional layer of abstraction and isolation, enabling you to package an application along with its dependencies, libraries, and configuration files into a standardized unit called a container.
A container is an environment for your code. Basically, a container has no knowledge of your OS(operating system as well as your files) It runs on the environment provided by Docker and it is a lightweight and standalone executable package that contains everything needed to run an application, including the code, runtime, system tools, system libraries, and settings. It encapsulates the application and its dependencies, ensuring consistency and portability across different computing environments, such as development machines, testing environments, and production servers.
What You’ll Learn
- Installing Docker on your local machine.
- Setting up a Maven project with the necessary dependencies.
- Using Testcontainers to create a mock server container.
- Writing and executing REST Assured tests.
- Managing the lifecycle of containers in your test suite.
Prerequisites
- Java Development Kit (JDK) 11 or higher.
- Maven (for dependency management).
- An IDE (like IntelliJ IDEA or Eclipse).
- Basic knowledge of REST APIs and Docker.
Step 1: Installing Docker
For Linux:
- Install Docker: Open a terminal and run:
- sudo apt update
sudo apt install docker.io
- Start Docker:
sudo systemctl start dockersudo systemctl enable docker
- Verify Installation:
docker --version
Step 2: Setting Up Your Maven Project for API Testing with Testcontainers
Create a new Maven project. Update your pom.xml to include dependencies for Testcontainers and REST Assured.
<dependencies>
<!-- JUnit Jupiter -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>5.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
<version>5.7.1</version>
<scope>test</scope>
</dependency>
<!-- Testcontainers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<version>1.16.0</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>1.16.0</version>
</dependency>
<!-- REST Assured for API testing -->
<dependency>
<groupId>io.rest-assured</groupId>
<artifactId>rest-assured</artifactId>
<version>4.3.3</version>
<scope>test</scope>
</dependency>
Step 3: API Testing on Test Containers using Test Assured Execution Flow
1.BaseTest.java
- Purpose: Base class with setup/teardown logic for tests.
- Execution: Sets up the environment before any tests, starting/stopping containers if needed.
package com.example;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
public class BaseTest {
protected ContainerManager containerManager;
@BeforeEach
public void setUp() {
containerManager = new ContainerManager();
// Start a container with default values; can be overridden in specific tests
containerManager.startContainer("nginx:latest", 80);
}
@AfterEach
public void tearDown() {
containerManager.stopContainer();
}
}
2.ContainerManager.java
- Purpose: Manages the Docker container lifecycle (starting/stopping).
- Execution: Initializes the container (e.g., pulls and starts the Docker image) before tests in
AppTest.java.
package com.example;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.utility.DockerImageName;
public class ContainerManager {
private GenericContainer<?> container;
public void startContainer(String imageName, int exposedPort) {
container = new GenericContainer<>(DockerImageName.parse(imageName))
.withExposedPorts(exposedPort);
container.start();
}
public String getContainerHost() {
return container.getHost();
}
public Integer getContainerPort(int exposedPort) {
return container.getMappedPort(exposedPort);
}
public void stopContainer() {
if (container != null) {
container.stop();
}
}
}
3.AppTest.java
- Purpose: Main test class with defined test cases.
- Execution:
@BeforeAllstarts the Nginx container.- Test methods (e.g.,
testServerHeader) run in sequence, sending requests to Nginx and making assertions. @AfterAllstops the container, cleaning up.
package com.example;
import org.junit.jupiter.api.Test;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import static io.restassured.RestAssured.given;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.lessThan;
import static org.junit.jupiter.api.Assertions.assertTrue;
@Testcontainers
public class AppTest {
@Container
public GenericContainer<?> container = new GenericContainer<>("nginx:latest")
.withExposedPorts(80);
private String getContainerUrl() {
Integer mappedPort = container.getMappedPort(80);
return "http://" + container.getHost() + ":" + mappedPort;
}
@Test
public void testContainerRunning() {
String containerAddress = getContainerUrl();
given()
.when()
.get(containerAddress)
.then()
.statusCode(200)
.body(containsString("Welcome to nginx!")); // Check welcome message
}
@Test
public void testContainerStatusCode() {
String containerAddress = getContainerUrl();
given()
.when()
.get(containerAddress)
.then()
.statusCode(200); // Check status code
}
@Test
public void testContentType() {
String containerAddress = getContainerUrl();
given()
.when()
.get(containerAddress)
.then()
.contentType("text/html"); // Updated to match actual content type
}
@Test
public void testResponseTime() {
String containerAddress = getContainerUrl();
given()
.when()
.get(containerAddress)
.then()
.time(lessThan(5000L)); // Response time should be less than 5 seconds
}
@Test
public void testServerHeader() {
String containerAddress = getContainerUrl();
given()
.when()
.get(containerAddress)
.then()
.header("Server", "nginx/1.27.1"); // Updated to match actual server version
}
@Test
public void testNotFound() {
String containerAddress = getContainerUrl();
String invalidEndpoint = containerAddress + "/invalid-path"; // Use a clearly invalid path
given()
.when()
.get(invalidEndpoint)
.then()
.statusCode(404); // Check for not found status
}
@Test
public void testPageTitle() {
String containerAddress = getContainerUrl();
String response = given()
.when()
.get(containerAddress)
.asString();
// Check if the response body contains the page title
assertTrue(response.contains("<title>Welcome to nginx!</title>"));
}
}
4.ApiRequestHelper.java
- Purpose: Utility class for reusable API request methods.
- Execution: Called by
AppTestto simplify sending requests to the container without duplicating request code.
package com.example;
import io.restassured.RestAssured;
import io.restassured.response.Response;
public class ApiRequestHelper {
public static Response sendGetRequest(String url) {
return RestAssured.given().when().get(url);
}
public static Response sendPostRequest(String url, Object body) {
return RestAssured.given()
.contentType("application/json")
.body(body)
.when()
.post(url);
}
public static Response sendPutRequest(String url, Object body) {
return RestAssured.given()
.contentType("application/json")
.body(body)
.when()
.put(url);
}
public static Response sendDeleteRequest(String url) {
return RestAssured.given().when().delete(url);
}
}
Step 5: Running the Test cases on API Testing with Testcontainers and REST Assured
To execute your tests, run the following command in your project directory:
mvn clean test
Step 6: Observing the Results
After running the command, you should see the output of your tests in the console. Each of your test cases will indicate whether they passed or failed.
Managing Container Lifecycle
Testcontainers handles the lifecycle of your containers automatically. Once the tests are completed, Testcontainers will stop and remove the containers, ensuring a clean slate for the next test run.
Conclusion
This guide provided a comprehensive overview of setting up containerized API testing using Testcontainers and REST Assured. By following the steps outlined, you can easily create a mock server environment, write robust tests, and ensure your APIs function as expected.
