REST-Assured Configuration and Specifications: Writing Maintainable API Tests
Learn how to use REST-Assured Configuration, Request Specifications, and Response Specifications to build maintainable API tests.
Join the DZone community and get the full member experience.
Join For FreeWhen working on API automation projects, one of the first things that becomes repetitive is configuring the same settings for every test. The base URL, content type, request logging, and common response validations often appear in multiple test classes. As the number of tests increases, maintaining these repeated configurations becomes difficult.
REST Assured provides specifications to solve this problem. Instead of defining the same settings in every test, common configurations and specifications can be created once and reused throughout the test suite.
This article demonstrates a simple approach to configuring REST Assured using a Base Test class along with Request and Response Specification.
What Are REST-Assured Specifications?
A specification is a reusable configuration object that contains common request or response settings. So, instead of repeatedly writing:
given()
.baseUri("https://api.example.com")
.header("Authorization", "Bearer token")
.contentType(ContentType.JSON)
The configuration can be defined once and reused across multiple tests. Similarly, the common validations can also be written using the specifications.
Specifications help in:
- Reduce code duplication
- Improve test readability
- Centralize API configurations
- Simplify maintenance
- Standardize request and response validations
Why Use Specifications?
Consider an API test that retrieves user details.
@Test
public void getUserDetails() {
given()
.baseUri("https://api.example.com")
.when()
.get("/orders/2")
.then()
.statusCode(200);
}
The test works correctly, but the base URI and common validations, such as status code, will need to be repeated in every test.
A better approach is to move these common settings into reusable specifications.
What Problem Does It Solve?
In many API automation projects, test cases often contain repeated configuration code. The same base URL, content type, authentication details, headers, and response validations are repetitive across multiple test classes. While this may not seem like a problem when there are only a few tests, maintaining the test suite becomes difficult as the project grows.
Consider a scenario where the API base URL changes from a QA environment to a Staging environment. Without a centralized configuration, every test containing the old URL would need to be updated. Similarly, if a common header or authentication mechanism changes, modifications would be required in multiple places.
Request and Response Specifications solve this problem by moving common configurations into reusable objects. Instead of repeating the same setup in every test, the configuration is defined once and reused wherever required. This reduces code duplication, improves readability, and makes the test suite easier to maintain.
As a result, test methods can focus on validating business functionality rather than configuring API requests and responses. This leads to cleaner and more maintainable automation code.
Creating a SetupSpecification Class
The most common configurations should be placed in a separate class. This allows all test classes to inherit the same setup.
The following example creates a Request and Response Specification in a separate class using the @BeforeClass annotation.
public class SetupSpecification {
@BeforeClass
public void setup () {
final RequestSpecification request = new RequestSpecBuilder ()
.addHeader ("Content-Type", "application/json")
.setBaseUri ("http://localhost:3004")
.addFilter (new RequestLoggingFilter ())
.addFilter (new ResponseLoggingFilter ())
.build ();
final ResponseSpecification response = new ResponseSpecBuilder ()
.expectResponseTime (lessThan (10000L))
.build ();
RestAssured.requestSpecification = request;
RestAssured.responseSpecification = response;
}
}
This setup method runs before the test class execution.
The Request Specification contains the base URI, content type, and logging configuration. Any configuration defined in a Request Specification will be applied to every API request that uses that specification.
For example, if the specification includes a common header, authentication token, content type, or query parameter, those values will automatically be sent with all requests that reference the specification.
While this promotes reusability and reduces duplication, care should be taken when adding request-specific details to a shared specification. Not all APIs may require the same headers, authentication mechanisms, query parameters, or request bodies. Including such configurations in a common specification can lead to unintended behavior and make tests more difficult to maintain.
The Response Specification contains the common validations that are expected from the API response. The expectResponseTime() method validates that the API responds within the specified time limit.
Additionally, we can also add the validations for:
- Status Code
- Headers
- Content-Type
- Cookie
- Body
However, it is important to understand that any validation defined in a Response Specification will be applied to every API test that uses that specification.
For example, if the specification includes a validation for a 200 status code, all tests using that specification will automatically expect a 200 response. This may not be appropriate for APIs that are expected to return different status codes, such as 201, 204, 400, or 404.
The same consideration applies to validations related to headers, content type, cookies, and response body content. Including endpoint-specific validations in a shared specification can reduce flexibility and make tests harder to maintain.
A good practice is to keep only the truly common validations in a shared Response Specification and add endpoint-specific assertions within the individual test methods.
The statement below makes the Request Specification available globally for the test execution.
RestAssured.requestSpecification = request;
RestAssured.responseSpecification = response;
As a result, the base URI and header(Content-Type), and validation to check the response time do not need to be specified in every test.
Writing a Test Using the Specifications
Once the setup is complete, test classes can extend the SetupSpecification class.
public class TestGetRequestWithRestAssuredSpecs extends SetupSpecification {
@Test
public void getRequestTestWithRestAssuredConfig () {
final int orderId = 3;
given ().when ()
.queryParam ("id", orderId)
.get ("/getOrder")
.then ()
.statusCode (200)
.and ()
.assertThat ()
.body ("orders[0].id", equalTo (orderId), "orders[0].product_name", equalTo ("USB-C Charger"));
}
}
The Request Specification is automatically applied because it was configured in the SetupSpecification class. It means all the common request configurations, such as the base URI, headers, content type, and logging settings, are automatically applied to the request.
Similarly, the common response validations configured for expected response time in the SetupSpecification class are reused during test execution.
The test itself focuses only on endpoint-specific details by passing the id query parameter, invoking the /getOrder endpoint.
This approach keeps the test concise and improves maintainability by separating common configuration from test-specific assertions.
Adding Additional Assertions
The Response Specification can handle common validations, while endpoint-specific assertions can still be added in the test.
public class TestGetRequestWithRestAssuredSpecs extends SetupSpecification {
@Test
public void getRequestTestWithRestAssuredConfig () {
final int orderId = 3;
given ().when ()
.queryParam ("id", orderId)
.get ("/getOrder")
.then ()
.statusCode (200)
.and ()
.assertThat ()
.body ("orders[0].id", equalTo (orderId), "orders[0].product_name", equalTo ("USB-C Charger"));
}
}
In this example, the response body validations for order ID and product name remain inside the test because they are specific to this API endpoint.
Why This Approach Is Useful
As the test suite grows, hundreds of API tests may use the same base URL, content type, authentication, and response validations. Maintaining these configurations in every test class can quickly become difficult.
Keeping the Request and Response Specifications in a separate class provides a centralized location for managing common settings. If the API URL changes or additional configurations need to be added, only a single file needs to be updated.
This approach also improves readability because the test methods contain only the business validations relevant to the API being tested.
Using Request and Response Specifications Directly in the Test Class
While many automation projects prefer keeping specifications in a separate class, there are situations where creating specifications directly inside the test class makes sense. This approach is useful for smaller projects, proof-of-concept implementations, or when a test class requires its own configuration that is not shared with other tests.
In this approach, the Request and Response Specifications are created using the @BeforeClass annotation and are available only within the current test class.
public class StringRelatedAssertionTests {
private static ResponseSpecification responseSpecification;
private static RequestSpecification requestSpecification;
@BeforeClass
public void setupSpecBuilder () {
final RequestSpecBuilder requestSpecBuilder = new RequestSpecBuilder ().setBaseUri (
"https://api.restful-api.dev/objects")
.addQueryParam ("id", 3)
.addFilter (new RequestLoggingFilter ())
.addFilter (new ResponseLoggingFilter ());
final ResponseSpecBuilder responseSpecBuilder = new ResponseSpecBuilder ().expectStatusCode (200);
responseSpecification = responseSpecBuilder.build ();
requestSpecification = requestSpecBuilder.build ();
}
@Test
public void testStringAssertions () {
given ().spec (requestSpecification)
.get ()
.then ()
.spec (responseSpecification)
.assertThat ()
.body ("[0].name", equalTo ("Apple iPhone 12 Pro Max"))
}
}
In this example, the Request and Response Specifications are created once in the @BeforeClass method and stored in static variables. The Request Specification contains common request details such as the base URI, query parameters, and logging filters, while the Response Specification defines the expected status code.
During test execution, the Request Specification is applied using the spec(requestSpecification) method before sending the request. After the response is received, the Response Specification is applied using spec(responseSpecification) to validate the common response expectations before performing additional assertions on the response body.
Keeping the specifications and test logic within the same class makes the example easy to follow, as both the setup and test execution are located in a single file. However, as the test suite grows and multiple test classes require the same configurations, duplicating specifications across classes can become difficult to maintain.
In such situations, moving the common Request and Response Specifications to a separate class provides better reusability and reduces code duplication. For smaller projects or learning purposes, defining the specifications directly within the test class remains a simple and effective approach.
Summary
Rest-Assured Specifications help create cleaner and more maintainable API automation tests. A best practice is to define Request and Response Specification in a separate class and initialize them using the @BeforeClass annotation.
The Request Specification manages settings such as the base URI, content type, and logging, while the Response Specification handles common response validations. By centralizing these configurations, test classes become shorter, easier to read, and simpler to maintain.
For API automation frameworks built with REST Assured and TestNG, this pattern provides a clean foundation that scales well as the number of tests increases.
Published at DZone with permission of Faisal Khatri. See the original article here.
Opinions expressed by DZone contributors are their own.

Comments