Introduction

In our previous post, we explored Response Validation in REST Assured, learning how to verify API responses. Now, we’ll dive into Data-Driven Testing, a technique that allows you to run the same API test with multiple data sets to ensure robustness and coverage. This guide is designed for beginners and experienced developers, providing clear explanations and practical examples to master data-driven testing in REST Assured.

Key Point: Data-driven testing enhances API test efficiency by executing the same test logic with different input data, reducing code duplication and improving test coverage.

What is Data-Driven Testing?

Data-Driven Testing involves running a single test case multiple times with different input data and expected outcomes. For API testing, this means sending requests with varied parameters, headers, or payloads and validating the responses. For example, testing a POST request to create users with different usernames and emails.

In REST Assured, data-driven testing is achieved using frameworks like JUnit’s Parameterized Tests or external data sources (e.g., CSV, JSON, or Excel files). This approach ensures your API handles various scenarios correctly.

Implementing Data-Driven Testing in REST Assured

Let’s explore how to implement data-driven testing using REST Assured with JUnit’s parameterized tests and external data sources. We’ll use the public API https://jsonplaceholder.typicode.com for examples.

Option 1: Using JUnit Parameterized Tests

JUnit’s @ParameterizedTest annotation allows you to run a test with multiple data sets provided via sources like @ValueSource, @CsvSource, or @MethodSource.

Ensure your pom.xml includes JUnit Jupiter for parameterized tests:


<dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter</artifactId>
    <version>5.10.2</version>
    <scope>test</scope>
</dependency>

Example using @CsvSource to test a GET request with different user IDs:


import io.restassured.RestAssured;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class ParameterizedGetTest {

    @ParameterizedTest
    @CsvSource({
        "1, Leanne Graham",
        "2, Ervin Howell",
        "3, Clementine Bauch"
    })
    public void testGetUser(int userId, String expectedName) {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .pathParam("userId", userId)
            .when()
                .get("/users/{userId}")
            .then()
                .statusCode(200)
                .body("id", equalTo(userId))
                .body("name", equalTo(expectedName));
    }
}

Explanation:

  • @ParameterizedTest: Marks the test as parameterized.
  • @CsvSource: Provides data as comma-separated values, where each row contains a userId and expectedName.
  • pathParam("userId", userId): Uses the parameterized userId in the URL.
  • body("name", equalTo(expectedName)): Validates the response name matches the expected value.
Important: Parameterized tests reduce repetitive code by running the same logic with different inputs, making it easier to test multiple scenarios.

Option 2: Using Method Source for Complex Data

For more complex data, use @MethodSource to provide a stream of test data.


import io.restassured.RestAssured;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
import java.util.stream.Stream;

public class MethodSourceTest {

    static Stream<Arguments> postDataProvider() {
        return Stream.of(
            Arguments.of("Test Post 1", "Body 1", 1, 201),
            Arguments.of("Test Post 2", "Body 2", 2, 201),
            Arguments.of("", "Body 3", 3, 400) // Invalid title, expecting error
        );
    }

    @ParameterizedTest
    @MethodSource("postDataProvider")
    public void testPostData(String title, String body, int userId, int expectedStatus) {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        String jsonBody = String.format("{\"title\": \"%s\", \"body\": \"%s\", \"userId\": %d}", title, body, userId);

        given()
            .contentType("application/json")
            .body(jsonBody)
            .when()
                .post("/posts")
            .then()
                .statusCode(expectedStatus)
                .body("userId", expectedStatus == 201 ? equalTo(userId) : nullValue());
    }
}

Explanation:

  • postDataProvider: Returns a stream of Arguments with test data (title, body, userId, expected status).
  • @MethodSource("postDataProvider"): Links the test to the data provider.
  • The test validates different POST scenarios, including an invalid case (empty title).

Option 3: Using External Data Source (CSV File)

Read test data from a CSV file for large or dynamic data sets. Use a library like OpenCSV to parse the file.

Add OpenCSV to your pom.xml:


<dependency>
    <groupId>com.opencsv</groupId>
    <artifactId>opencsv</artifactId>
    <version>5.9</version>
    <scope>test</scope>
</dependency>

Create a CSV file (e.g., testdata.csv) in src/test/resources:


userId,expectedName
1,Leanne Graham
2,Ervin Howell
3,Clementine Bauch

Test using the CSV data:


import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvValidationException;
import io.restassured.RestAssured;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

public class CsvDataDrivenTest {

    static Stream<Object[]> csvDataProvider() throws IOException, CsvValidationException {
        List<Object[]> data = new ArrayList<>();
        try (CSVReader csvReader = new CSVReader(new FileReader("src/test/resources/testdata.csv"))) {
            csvReader.readNext(); // Skip header
            String[] row;
            while ((row = csvReader.readNext()) != null) {
                data.add(new Object[]{Integer.parseInt(row[0]), row[1]});
            }
        }
        return data.stream();
    }

    @ParameterizedTest
    @MethodSource("csvDataProvider")
    public void testGetUserFromCsv(int userId, String expectedName) {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .pathParam("userId", userId)
        .when()
            .get("/users/{userId}")
        .then()
            .statusCode(200)
            .body("name", equalTo(expectedName));
    }
}

Explanation:

  • csvDataProvider: Reads the CSV file and returns a stream of data (userId, expectedName).
  • CSVReader: Parses the CSV file using OpenCSV.
  • The test runs once for each row in the CSV file.
Pro Tip: External data sources like CSV or JSON files are ideal for large data sets or when data is maintained by non-developers (e.g., QA teams).

Step 1: Combining Data-Driven Testing with Request Specification

Use a Request Specification with parameterized tests for consistent settings.


import io.restassured.RestAssured;
import io.restassured.builder.RequestSpecBuilder;
import io.restassured.specification.RequestSpecification;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

public class DataDrivenWithSpecTest {

    private static RequestSpecification requestSpec;

    @BeforeAll
    public static void setup() {
        requestSpec = new RequestSpecBuilder()
            .setBaseUri("https://jsonplaceholder.typicode.com")
            .addHeader("Accept", "application/json")
            .build();
    }

    @ParameterizedTest
    @CsvSource({
        "1, Leanne Graham",
        "2, Ervin Howell"
    })
    public void testGetUserWithSpec(int userId, String expectedName) {
        given()
            .spec(requestSpec)
            .pathParam("userId", userId)
            .when()
                .get("/users/{userId}")
            .then()
                .statusCode(200)
                .body("name", equalTo(expectedName));
    }
}

Explanation:

  • requestSpec: Defines common settings like base URI and headers.
  • spec(requestSpec): Applies the specification to each parameterized test.

Step 2: Testing POST Requests with Data-Driven Approach

Apply data-driven testing to POST requests using a Java object for the request body.

Ensure Jackson is in your pom.xml for JSON serialization:


<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.15.2</version>
    <scope>test</scope>
</dependency>

Test example:


import io.restassured.RestAssured;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;
import java.util.stream.Stream;

// Java class for request body
class Post {
    private String title;
    private String body;
    private int userId;

    public Post(String title, String body, int userId) {
        this.title = title;
        this.body = body;
        this.userId = userId;
    }

    // Getters and setters
    public String getTitle() { return title; }
    public void setTitle(String title) { this.title = title; }
    public String getBody() { return body; }
    public void setBody(String body) { this.body = body; }
    public int getUserId() { return userId; }
    public void setUserId(int userId) { this.userId = userId; }
}

public class DataDrivenPostTest {

    static Stream<Arguments> postDataProvider() {
        return Stream.of(
            Arguments.of("Test Post 1", "Body 1", 1),
            Arguments.of("Test Post 2", "Body 2", 2)
        );
    }

    @ParameterizedTest
    @MethodSource("postDataProvider")
    public void testPostData(String title, String body, int userId) {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        Post post = new Post(title, body, userId);

        given()
            .contentType("application/json")
            .body(post)
            .when()
                .post("/posts")
            .then()
                .statusCode(201)
                .body("title", equalTo(title))
                .body("userId", equalTo(userId));
    }
}

Explanation:

  • Post: Java class for the request body.
  • postDataProvider: Supplies data for the POST request.
  • The test creates posts with different data and validates the response.

Step 3: Verify Setup with pom.xml

Ensure your pom.xml includes all required dependencies:


<dependencies>
    <dependency>
        <groupId>io.rest-assured</groupId>
        <artifactId>rest-assured</artifactId>
        <version>5.4.0</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter</artifactId>
        <version>5.10.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.hamcrest</groupId>
        <artifactId>hamcrest</artifactId>
        <version>2.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
        <version>2.15.2</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>com.opencsv</groupId>
        <artifactId>opencsv</artifactId>
        <version>5.9</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Run the tests using mvn test or your IDE’s test runner to confirm the setup.

Tips for Beginners

  • Start Simple: Begin with @ValueSource or @CsvSource for small data sets.
  • Use External Files: For large data sets, use CSV or JSON files to keep data separate from code.
  • Validate Thoroughly: Ensure each test case includes expected outcomes for robust validation.
  • Enable Logging: Use RestAssured.enableLoggingOfRequestAndResponseIfValidationFails() to debug test failures.
Troubleshooting Tip: If parameterized tests fail, verify the data source format (e.g., CSV structure) and ensure the API handles each input correctly. Use logging to inspect requests and responses.

What’s Next?

In the next post, we’ll cover Serialization and Deserialization, exploring how to convert Java objects to JSON and vice versa in REST Assured for seamless API testing. Stay tuned for more hands-on examples!