RestAssured can be used not only for sending HTTP requests and asserting response body and headers. It also can measure the response time and assert it against a condition you set. In this blog post, I describe the way how to send multiple parallel requests with RestAssured and a data provider from TestNG.
Introduction
Consider the following scenario. You have to execute the same test method with multiple test data values against a REST API endpoint. Moreover, you have to run all of them in parallel at the same time. A real-world scenario could be to check how a service under test will respond to multiple requests sent simultaneously, to test the response time, for example.
For REST API testing, a common tool in Java world is the RestAssured library. I assume that you have already some experience with this library (if not, check its getting started guide and documentation), so let’s set up first request specification.
RestAssured specification configuration
In this presentation I will solve the following task: having a list of certain codes/IDs, I need to send the same POST request towards a service endpoint each with its code/ID from the list.
Let’s first set up RestAssured specification configuration. I want to reuse the same configuration for all test
methods in my class, so I’ll define first the specification as a class field and will put the builder configuration
in @BeforeMethod
of TestNG.
RequestSpecification specification;
@BeforeMethod
public void setUpSpecification(){
RequestSpecBuilder builder = new RequestSpecBuilder();
builder.addQueryParam(parameterName, parameterValue); // define common parameters
builder.setContentType(ContentType.JSON);
specification = builder.build();
}
So, each of the tests in my test class will reuse the same specification.
Using TestNG DataProvider
Since the task assumes that I will use the same test method with different data values, TestNG DataProvider feature
will help to make this easy to set up without code duplication. What DataProvider
is - it is a method that supplies
test method with its values one by one so that test will continue to execute as long as DataProvider
will continue
to supply its values. You can hard code values in DataProvider
method, or you can define an implementation of
getting test values from another source (file, database or, another method or class).
DataProvider
method should return an array of type Object
, either a one-dimensional or multi-dimensional.
For simplicity, I will use a one-dimensional array with hard-coded test values.
@DataProvider(parallel = true)
public Object[] getId() {
return new Object[] {"KD3856", "DK8937", "EF7301"};
}
@DataProvider
annotation accepts several arguments as configurations. One of them is for example name
which will
set a custom name for your DataProvider. If this argument is omitted, the method’s name will be taken as a name.
Another argument is boolean parallel
, which is set to false
by default. Here’s what happens when you set the
value for this argument to true
.
DataProvider will supply its test data values to test method in parallel, each in its own thread. Thus, the test
method will be executed also in parallel, which will significantly improve the overall test execution time. If we
would leave the default value for the parallel
argument, then the test will be executed one by one.
Set up a test method
Once DataProvider is set up, now it’s time to set up test method and connect it to a DataProvider. Here’s the whole test method that sends a POST request.
@Test(dataProvider = "getId")
public void responseTimeOfEndpoint(String id) {
Map<String, String> map = new HashMap<>();
map.put("id", id);
given()
.spec(specification)
.body(map)
.when()
.log()
.all()
.post(ENDPOINT_ADDRESS)
.then()
.time(lessThan(2000L))
.and()
.statusCode(200);
}
Let’s describe this test method in detail.
TestNG @Test
annotation accepts argument dataProvider
that points to a name of a DataProvider that we want to
use. We set DataProvider’s method name since we didn’t specify a custom name for a DataProvider.
In order for the test method to get a test value from a DataProvider, test method should accept arguments of the same type as DataProvider’s test value is. In our example, DataProvider returns an array of strings. Thus, the test method should accept an argument of a String type. If we would have a multi-dimensional String array in DataProvider, then the test method would accept two String arguments.
When we need to use the test data value, then we use this argument variable. So we did in map.put("id", id);
expression when creating a body for a POST request.
Next goes a pretty common RestAssured DSL for a POST method that asserts that a response is received within 2 seconds and its status is 200 OK.
Set up a number of threads for a DataProvider
When you set a parallel
argument of @DataProvider
annotation to true
you also need to specify the number of
parallel threads to be open.
That is done in two ways:
- as a command line argument passed to Maven
- as a configuration of Maven Surefire Plugin
Let’s describe both options.
Number of parallel threads passed as a command line argument
Just include the following parameter in your Maven command with the number of threads you need to open:
-Ddataproviderthreadcount=30
.
To run tests within IntelliJ IDEA, use the same syntax in “VM options” field of TestNG Run Configuration.
Number of parallel threads as a configuration of Maven Surefire Plugin
Another option of setting up the number of threads is to specify this configuration in Maven Surefire Plugin if it is
used it your project. This way you keep your configuration in pom.xml
and don’t add yet another argument to your
pipeline command.
The configuration is pretty simple. Set the dataproviderthreadcount
in property name and number of threads in
property value. Find below the full plugin declaration:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.0</version>
<configuration>
<properties>
<property>
<name>dataproviderthreadcount</name>
<value>30</value>
</property>
</properties>
</configuration>
</plugin>
Conclusion
In this blog post, I showed the way how you can easily set up TestNG and RestAssured to run the same test in parallel using a set of test data values. So this technique can be used for a sort of load testing or performance testing of your REST endpoints.
Just one drawback in the current implementation of parallel DataProvider execution is that you set the number of threads globally per project and you can’t control it per test method or per test class.
In case if the number of the test data values in DataProvider is less than the number of threads set, TestNG will open the number of the threads that is equal to a number of test data values available in DataProvider.