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 named username.
  • log().all(): Logs the request, showing the multipart/form-data content type and boundary.
  • body("form.username", ...): Verifies the echoed form data in the response.
Important: REST Assured automatically sets the Content-Type to multipart/form-data when using multiPart().

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 and files 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 a Bearer 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 and mvn 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.
Stay tuned for more testing tutorials!