Introduction
In our REST Assured series, we’ve explored topics like Session Management, Parallel Execution, and API Chaining, building a strong foundation for API testing. Now, we’ll dive into Multi-Part Form Data, a technique for sending files and complex form data in API requests. This guide demonstrates how to use REST Assured to handle multi-part requests, with practical examples and best practices. It’s designed for beginners and experienced developers, ensuring you can confidently test APIs that require file uploads or mixed data.
Key Point: Multi-Part Form Data in REST Assured enables you to send files, text, and other data in a single HTTP request, ideal for testing APIs that handle uploads or complex form submissions.
What is Multi-Part Form Data?
Multi-Part Form Data is an HTTP content type (multipart/form-data
) used to send multiple parts, such as text fields and files, in a single request. Each part has its own content type and name, separated by a boundary. Common use cases include:
- File Uploads: Sending images, PDFs, or other files to an API.
- Complex Forms: Combining text fields, metadata, and files in one request.
- API Testing: Validating endpoints that process multi-part data.
REST Assured simplifies multi-part requests with the multiPart()
method, handling boundary creation and content encoding. We’ll use https://httpbin.org/post
for examples, which echoes the request data, allowing us to verify the multi-part content. For file uploads, we’ll create a temporary text file programmatically to avoid local file dependencies.
Setting Up for Multi-Part Form Data Testing
We’ll set up a Maven project with REST Assured, JUnit 5, and Allure for reporting, consistent with previous posts. No additional dependencies are needed for multi-part support.
Here’s the pom.xml
, styled with your preferred Blogger format for XML syntax highlighting:
4.0.0
com.example
rest-assured-multipart
1.0-SNAPSHOT
11
2.27.0
1.9.22
io.rest-assured
rest-assured
5.4.0
test
org.junit.jupiter
junit-jupiter
5.10.2
test
org.hamcrest
hamcrest
2.2
test
com.fasterxml.jackson.core
jackson-databind
2.15.2
test
io.qameta.allure
allure-junit5
${allure.version}
test
org.apache.maven.plugins
maven-surefire-plugin
3.2.5
-javaagent:"${settings.localRepository}/org/aspectj/aspectjweaver/${aspectj.version}/aspectjweaver-${aspectj.version}.jar"
org.aspectj
aspectjweaver
${aspectj.version}
io.qameta.allure
allure-maven
2.12.0
${allure.version}
No additional setup is required for https://httpbin.org
. For file upload examples, we’ll create temporary files programmatically to ensure portability.
Using Multi-Part Form Data in REST Assured
Let’s explore how to send multi-part form data, including text fields and files, using REST Assured, with examples that demonstrate various scenarios.
Example 1: Sending Text Fields as Multi-Part Data
Send text fields in a multi-part request and verify the response.
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
@Feature("Multi-Part Form Data Testing")
public class MultiPartTextTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://httpbin.org";
}
@Test
@DisplayName("Test sending text fields as multi-part form data")
@Description("Send text fields in a multi-part request and verify the response")
public void testMultiPartText() {
given()
.multiPart("username", "john_doe")
.multiPart("email", "john@example.com")
.log().all()
.when()
.post("/post")
.then()
.log().all()
.statusCode(200)
.body("form.username", equalTo("john_doe"))
.body("form.email", equalTo("john@example.com"));
}
}
Explanation:
multiPart("username", "john_doe")
: Adds a text field namedusername
.log().all()
: Logs the request, showing themultipart/form-data
content type and boundary.body("form.username", ...)
: Verifies the echoed form data in the response.
Important: REST Assured automatically sets theContent-Type
tomultipart/form-data
when usingmultiPart()
.
Example 2: Uploading a File as Multi-Part Data
Upload a programmatically created text file in a multi-part request.
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.*;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
@Feature("Multi-Part Form Data Testing")
public class MultiPartFileUploadTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://httpbin.org";
}
@Test
@DisplayName("Test uploading a file as multi-part form data")
@Description("Upload a programmatically created text file and verify the response")
public void testFileUpload() throws IOException {
// Create a temporary file
File tempFile = File.createTempFile("test", ".txt");
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write("Hello, REST Assured!");
}
given()
.multiPart("file", tempFile, "text/plain")
.log().all()
.when()
.post("/post")
.then()
.log().all()
.statusCode(200)
.body("files.file", equalTo("Hello, REST Assured!"));
// Clean up
tempFile.delete();
}
}
Explanation:
File.createTempFile(...)
: Creates a temporary text file for portability.multiPart("file", tempFile, "text/plain")
: Uploads the file with the specified MIME type.body("files.file", ...)
: Verifies the file content in the response.- The file is deleted after the test to avoid clutter.
Example 3: Combining Text and File in Multi-Part Data
Send text fields and a file in a single multi-part request.
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.*;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
@Feature("Multi-Part Form Data Testing")
public class MultiPartMixedTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://httpbin.org";
}
@Test
@DisplayName("Test sending text and file as multi-part form data")
@Description("Send a text field and a file in a multi-part request and verify the response")
public void testMixedMultiPart() throws IOException {
// Create a temporary file
File tempFile = File.createTempFile("test", ".txt");
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write("Mixed data test");
}
given()
.multiPart("username", "john_doe")
.multiPart("file", tempFile, "text/plain")
.log().all()
.when()
.post("/post")
.then()
.log().all()
.statusCode(200)
.body("form.username", equalTo("john_doe"))
.body("files.file", equalTo("Mixed data test"));
// Clean up
tempFile.delete();
}
}
Explanation:
- Combines a text field (
username
) and a file (file
) in one request. - Verifies both parts in the response using
form
andfiles
paths.
Pro Tip: Use descriptive names for multi-part fields to match API expectations, and always specify the correct MIME type for files.
Example 4: Multi-Part with Custom Filter for Authentication
Use a custom filter to add a Bearer token to a multi-part request.
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.filter.Filter;
import io.restassured.filter.FilterContext;
import io.restassured.response.Response;
import io.restassured.specification.FilterableRequestSpecification;
import io.restassured.specification.FilterableResponseSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.*;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
@Feature("Multi-Part Form Data Testing")
public class MultiPartWithAuthFilterTest {
public static class BearerTokenFilter implements Filter {
@Override
public Response filter(FilterableRequestSpecification requestSpec,
FilterableResponseSpecification responseSpec,
FilterContext ctx) {
requestSpec.header("Authorization", "Bearer my-bearer-token");
return ctx.next(requestSpec, responseSpec);
}
}
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://httpbin.org";
}
@Test
@DisplayName("Test multi-part form data with Bearer token filter")
@Description("Send a multi-part request with a Bearer token and verify the response")
public void testMultiPartWithAuth() throws IOException {
// Create a temporary file
File tempFile = File.createTempFile("test", ".txt");
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write("Authenticated upload");
}
given()
.filter(new BearerTokenFilter())
.multiPart("file", tempFile, "text/plain")
.log().all()
.when()
.post("/post")
.then()
.log().all()
.statusCode(200)
.body("files.file", equalTo("Authenticated upload"))
.body("headers.Authorization", equalTo("Bearer my-bearer-token"));
// Clean up
tempFile.delete();
}
}
Explanation:
BearerTokenFilter
: Adds aBearer
token to the request.- Combines authentication with multi-part data, simulating a secure upload.
- Verifies both the file content and the
Authorization
header.
Example 5: Multi-Part with Response Specification
Combine multi-part requests with a ResponseSpecification
for reusable validation.
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.builder.ResponseSpecBuilder;
import io.restassured.specification.ResponseSpecification;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.*;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
@Feature("Multi-Part Form Data Testing")
public class MultiPartWithResponseSpecTest {
private ResponseSpecification responseSpec;
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://httpbin.org";
responseSpec = new ResponseSpecBuilder()
.expectStatusCode(200)
.expectContentType("application/json")
.expectBody("form.username", equalTo("john_doe"))
.build();
}
@Test
@DisplayName("Test multi-part form data with response specification")
@Description("Send a multi-part request with a response specification and verify the response")
public void testMultiPartWithSpec() throws IOException {
// Create a temporary file
File tempFile = File.createTempFile("test", ".txt");
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write("Spec test");
}
given()
.multiPart("username", "john_doe")
.multiPart("file", tempFile, "text/plain")
.log().all()
.when()
.post("/post")
.then()
.log().all()
.spec(responseSpec)
.body("files.file", equalTo("Spec test"));
// Clean up
tempFile.delete();
}
}
Explanation:
responseSpec
: Defines a reusable specification for multi-part responses.spec(responseSpec)
: Applies the specification to validate the response.- Additional test-specific checks verify the file content.
Integrating with Allure Reporting
Document multi-part tests with Allure, attaching request and response details.
import io.qameta.allure.Allure;
import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import io.restassured.response.Response;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import java.io.*;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
@Feature("Multi-Part Form Data Testing")
public class AllureMultiPartTest {
@BeforeEach
public void setup() {
RestAssured.baseURI = "https://httpbin.org";
}
@Test
@DisplayName("Test multi-part form data with Allure reporting")
@Description("Send a multi-part request and attach response details to Allure report")
public void testMultiPartWithAllure() throws IOException {
// Create a temporary file
File tempFile = File.createTempFile("test", ".txt");
try (FileWriter writer = new FileWriter(tempFile)) {
writer.write("Allure test");
}
Response response = given()
.multiPart("username", "john_doe")
.multiPart("file", tempFile, "text/plain")
.log().all()
.when()
.post("/post")
.then()
.log().all()
.statusCode(200)
.body("form.username", equalTo("john_doe"))
.body("files.file", equalTo("Allure test"))
.extract().response();
Allure.addAttachment("Response", "application/json", response.asString(), ".json");
// Clean up
tempFile.delete();
}
}
Explanation:
Allure.addAttachment
: Attaches the response body to the Allure report.- Logs request and response details with
log().all()
. - Run
mvn clean test
andmvn allure:serve
to view the report.
Integrating with CI/CD
Add multi-part tests to a GitHub Actions pipeline for automation.
Create or update .github/workflows/ci.yml
:
name: REST Assured Multi-Part CI Pipeline
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 11
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
- name: Cache Maven dependencies
uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: ${{ runner.os }}-maven-
- name: Run tests
run: mvn clean test
- name: Generate Allure report
run: mvn allure:report
- name: Upload Allure results
if: always()
uses: actions/upload-artifact@v4
with:
name: allure-results
path: target/allure-results
- name: Publish Allure report
if: always()
uses: simple-elf/allure-report-action@v1.7
with:
allure_results: target/allure-results
gh_pages: gh-pages
allure_report: allure-report
- name: Deploy report to GitHub Pages
if: always()
uses: peaceiris/actions-gh-pages@v4
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: allure-report
Explanation:
- Runs multi-part tests with
mvn clean test
. - Publishes Allure reports to GitHub Pages for visibility.
Tips for Beginners
- Use Temporary Files: Create files programmatically for tests to ensure portability across environments.
- Verify MIME Types: Specify correct MIME types (e.g.,
text/plain
,image/jpeg
) for files to match API expectations. - Log Requests: Use
log().all()
to inspect multi-part boundaries and content. - Test Edge Cases: Validate API behavior for missing fields, large files, or invalid MIME types.
Troubleshooting Tip: If a multi-part request fails, enable log().all()
to inspect the request body and verify field names, MIME types, and file content against the API documentation.
What’s Next?
This post enhances our REST Assured series by covering Multi-Part Form Data, a key skill for testing file uploads and complex forms. To continue your learning, consider exploring:
- GraphQL Testing: Testing GraphQL APIs with REST Assured.
- Advanced Allure Reporting: Customizing reports with environments and categories.
- End-to-End Testing: Combining REST Assured with Selenium for UI and API testing.