Introduction

In our REST Assured series, we’ve covered topics like JSON Schema Validation, File Uploads and File Downloads, and Multi-Part Form Data, equipping you with tools to test diverse API scenarios. Now, we’ll explore Hamcrest Matchers, a powerful library integrated with REST Assured for creating expressive and readable assertions. This guide demonstrates how to use Hamcrest Matchers to verify API responses, with practical examples and best practices. It’s designed for beginners and experienced developers, ensuring you can write clear and robust API tests.

Key Point: Hamcrest Matchers in REST Assured provide a fluent and flexible way to assert API response data, making tests more readable and maintainable.

What are Hamcrest Matchers?

Hamcrest is a framework for writing matcher objects that perform assertions in a declarative, human-readable way. In REST Assured, Hamcrest Matchers are used within the body() method to validate JSON, XML, or other response data. They support a wide range of assertions, including:

  • Basic Comparisons: Equality, null checks, and string matching.
  • Collection Assertions: Verifying lists, arrays, or maps.
  • Complex Conditions: Combining matchers or creating custom logic.

REST Assured includes Hamcrest as a dependency, so no additional setup is needed. We’ll use https://jsonplaceholder.typicode.com for examples, testing endpoints like /posts and /comments to demonstrate matchers for various data types and structures.

Setting Up for Hamcrest Matchers Testing

We’ll set up a Maven project with REST Assured, JUnit, and Allure for reporting, consistent with previous posts. The hamcrest dependency is already included via REST Assured.

Here’s the pom.xml, styled with your preferred Blogger format for XML syntax highlighting:



    4.0.0
    com.example
    rest-assured-tests
    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://jsonplaceholder.typicode.com. All examples will use its endpoints to demonstrate Hamcrest Matchers.

Using Hamcrest Matchers in REST Assured

Let’s explore how to use Hamcrest Matchers for various assertion scenarios.

Example 1: Basic Matchers for Single Fields

Use basic matchers to assert field values in a single post response.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Hamcrest Matchers Testing")
public class BasicMatchersTest {

    @Test
    @Description("Test basic Hamcrest Matchers for single fields")
    public void testBasicMatchers() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().all()
        .when()
            .get("/posts/1")
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("userId", greaterThan(0))
            .body("title", notNullValue())
            .body("body", containsString("est"));
    }
}

Explanation:

  • equalTo(1): Asserts the id is exactly 1.
  • greaterThan(0): Ensures userId is positive.
  • notNullValue(): Verifies title is not null.
  • containsString("est"): Checks if body contains the substring "est".
Important: Use specific matchers like equalTo or containsString to make assertions clear and precise.

Example 2: Collection Matchers for Arrays

Use collection matchers to assert properties of a list of posts.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Hamcrest Matchers Testing")
public class CollectionMatchersTest {

    @Test
    @Description("Test Hamcrest Matchers for collections")
    public void testCollectionMatchers() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().all()
        .when()
            .get("/posts")
        .then()
            .statusCode(200)
            .body("", hasSize(100))
            .body("id", everyItem(greaterThan(0)))
            .body("title", hasItem(notNullValue()));
    }
}

Explanation:

  • hasSize(100): Asserts the response array contains 100 posts.
  • everyItem(greaterThan(0)): Ensures all id values are positive.
  • hasItem(notNullValue()): Verifies at least one title is not null.

Example 3: Nested Path Matchers

Use matchers to assert nested fields in a comment response.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Hamcrest Matchers Testing")
public class NestedPathMatchersTest {

    @Test
    @Description("Test Hamcrest Matchers for nested paths")
    public void testNestedPathMatchers() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().all()
        .when()
            .get("/comments/1")
        .then()
            .statusCode(200)
            .body("postId", equalTo(1))
            .body("email", matchesPattern("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$"))
            .body("name", hasLength(greaterThan(5)));
    }
}

Explanation:

  • matchesPattern(...): Validates the email field against a regex for email format.
  • hasLength(greaterThan(5)): Ensures the name field is longer than 5 characters.
  • Nested paths are accessed using dot notation (e.g., email).
Pro Tip: Use regex-based matchers like matchesPattern for complex string validations, such as emails or URLs.

Example 4: Combining Matchers

Combine multiple matchers to create complex assertions.


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Hamcrest Matchers Testing")
public class CombinedMatchersTest {

    @Test
    @Description("Test combining multiple Hamcrest Matchers")
    public void testCombinedMatchers() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().all()
        .when()
            .get("/posts/1")
        .then()
            .statusCode(200)
            .body("userId", allOf(greaterThan(0), lessThanOrEqualTo(10)))
            .body("title", both(notNullValue()).and(hasLength(greaterThan(10))));
    }
}

Explanation:

  • allOf(greaterThan(0), lessThanOrEqualTo(10)): Ensures userId is between 1 and 10.
  • both(notNullValue()).and(hasLength(...)): Verifies title is not null and longer than 10 characters.
  • Combinators like allOf and both enable complex conditions.

Example 5: Custom Hamcrest Matcher

Create a custom matcher to validate a specific condition (e.g., title starts with a capital letter).


import io.qameta.allure.Description;
import io.qameta.allure.Feature;
import io.restassured.RestAssured;
import org.hamcrest.BaseMatcher;
import org.hamcrest.Description;
import org.junit.jupiter.api.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Hamcrest Matchers Testing")
public class CustomMatcherTest {

    public static class CapitalizedTitleMatcher extends BaseMatcher {
        @Override
        public boolean matches(Object item) {
            if (item == null) return false;
            String title = item.toString();
            return !title.isEmpty() && Character.isUpperCase(title.charAt(0));
        }

        @Override
        public void describeTo(Description description) {
            description.appendText("a string starting with a capital letter");
        }
    }

    @Test
    @Description("Test custom Hamcrest Matcher for capitalized title")
    public void testCustomMatcher() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        given()
            .log().all()
        .when()
            .get("/posts/1")
        .then()
            .statusCode(200)
            .body("title", new CapitalizedTitleMatcher());
    }
}

Explanation:

  • CapitalizedTitleMatcher: Extends BaseMatcher to check if a title starts with a capital letter.
  • matches(...): Defines the matching logic.
  • describeTo(...): Provides a human-readable failure message.

Integrating with Allure Reporting

Document Hamcrest Matcher 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.Test;
import static io.restassured.RestAssured.*;
import static org.hamcrest.Matchers.*;

@Feature("Hamcrest Matchers Testing")
public class AllureMatchersTest {

    @Test
    @Description("Test Hamcrest Matchers with Allure reporting")
    public void testMatchersWithAllure() {
        RestAssured.baseURI = "https://jsonplaceholder.typicode.com";

        Response response = given()
            .log().all()
        .when()
            .get("/posts/1")
        .then()
            .statusCode(200)
            .body("id", equalTo(1))
            .body("userId", greaterThan(0))
            .body("title", notNullValue())
            .extract().response();

        Allure.addAttachment("Response Body", "application/json", response.asString(), ".json");
    }
}

Explanation:

  • Allure.addAttachment: Attaches the response body to the Allure report.
  • Run mvn clean test and mvn allure:serve to view the report.

Integrating with CI/CD

Add Hamcrest Matcher tests to a GitHub Actions pipeline for automation.

Create or update .github/workflows/ci.yml:


name: REST Assured 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 Hamcrest Matcher tests with mvn clean test.
  • Publishes Allure reports to GitHub Pages for visibility.

Tips for Beginners

  • Use Descriptive Matchers: Choose matchers like equalTo or containsString for clarity over generic ones.
  • Combine Matchers Sparingly: Avoid overly complex combinations to keep tests readable.
  • Debug with Logging: Use log().all() to inspect response data when matchers fail.
  • Create Custom Matchers: Write custom matchers for domain-specific validations to reuse logic.
Troubleshooting Tip: If a matcher fails, enable log().all() to inspect the response JSON and verify the path and expected value.

What’s Next?

This post enhances our REST Assured series by introducing Hamcrest Matchers, a key tool for expressive API assertions. 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!