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 theid
is exactly 1.greaterThan(0)
: EnsuresuserId
is positive.notNullValue()
: Verifiestitle
is not null.containsString("est")
: Checks ifbody
contains the substring "est".
Important: Use specific matchers likeequalTo
orcontainsString
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 allid
values are positive.hasItem(notNullValue())
: Verifies at least onetitle
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 theemail
field against a regex for email format.hasLength(greaterThan(5))
: Ensures thename
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))
: EnsuresuserId
is between 1 and 10.both(notNullValue()).and(hasLength(...))
: Verifiestitle
is not null and longer than 10 characters.- Combinators like
allOf
andboth
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
: ExtendsBaseMatcher
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
andmvn 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
orcontainsString
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.