DEV Community

Elias Nogueira
Elias Nogueira

Posted on

The best way to add a Request Body to a POST request using Rest-Assured

Introduction

One of my activities is to review the Quality Engineers’ technical assignment. We ask the candidate to automate at least 2 features in the API and Web layers.

Most of the candidates are delivering the API test automation solution using Java and Rest-Assured, which is the same combination we use. Of course, we are evaluating it based on the candidate experience in general which includes the whole testing experience, test automation experience, and libraries experience.

Rest-Assured is a well-known library to use for API test automation, simple to use, and fast. But I can see one gap in most of the solutions they delivered: the lack of knowledge about the library. It’s in different ways and one is unanimous: the use of either String or HashMap in the requests.

The intention here is to tell you which way you can have most of the professional benefits using Rest-Assured as I refer it here as the recommended approach.

The test implementation

To not add some complexity here and stay focused on our main goal we will use the https://reqres.in/, a mock API ready to respond to any request for testing purposes.

As the goal is sent a request with an object with it, sometimes referred to as the payload object, we will use the following endpoint:

Image description

Given the request body, we must send the name and job attributes, which are text (strings).

Working approaches

String concatenation (not recommended)

The string concatenation approach consists of creating a string with the same output as JSON.

Note that, on line 5, the String user is a string concatenation of the JSON Object we must send into the request.
As we are using Java we must escape all the double quotes, as a string starts and ends with it.
The validation on lines 15 and 16 must be hardcoded as well because there is no way to retrieve the data sent because it is a string.

class PostTest {

    @Test
    void postUsingStringConcatenation() {
        String user = "{\"name\":\"Elias\",\"job\":\"Principal Engineer\"}";

        given().
            contentType(ContentType.JSON).
            body(user).
        when().
            post("/users").
        then().
            statusCode(HttpStatus.SC_CREATED).
            body(
                "name", is("Elias"),
                "job", is("Principal Engineer"),
                "id", notNullValue(),
                "createdAt", notNullValue()
            );
    }
}
Enter fullscreen mode Exit fullscreen mode

We can see clearly that this approach works but will give you a lot of maintenance and trouble. You must avoid it.

Advantages

  • none

Disadvantages

  • hard to read
  • hard to maintain
  • looks unprofessional
  • does not allow easy object update (properties or values)

Using a HashMap (not recommended, but acceptable)

You can see this approach described in the Rest-Assured documentation but it does not mean this is the best approach.

This approach consists of creating a HashMap object to send it on the request.
As a HashMap as a key-value map where the key is the attribute and value is the attribute value, it seems a good candidate to use.

On line 5 we are using a Map typing the attribute as String and the value as an Object. This is not the case but you know that a JSON value can be an object.

On lines 6 and 7 we are adding the key (attribute name) and value (the value we want to send).

Behind the scene the binding library will transform the HashMap into a JSON object like this:

{ "name": "Elias", "job": "Principal Engineer" }
Enter fullscreen mode Exit fullscreen mode

class PostTest {

    @Test
    void postUsingHashMap() {
        Map<String, Object> user = new HashMap<>();
        user.put("name", "Elias");
        user.put("job", "Principal Engineer");

        given().
            contentType(ContentType.JSON).
            body(user).
        when().
            post("/users").
        then().
            statusCode(HttpStatus.SC_CREATED).
            body(
                "name", is(user.get("name")),
                "job", is(user.get("job")),
                "id", notNullValue(),
                "createdAt", notNullValue()
            );
    }
}
Enter fullscreen mode Exit fullscreen mode

This approach is better than the String concatenation and looks more professional, but it’s not the best one…

Advantages

  • Give you a clear vision of the attributes and values
  • Can enable you to go further and create a class placing the objects, like a test data factory approach

Disadvantages

  • It’s not flexible to create complex objects, as you need to create several HashMap
  • An update can cause confusion as you need to set the value in the same key. E.g: put("name", "New value")
  • Any typo in the keys will lead to errors that might be hard to find. Eg: put("name", "Elias") as we have a typo on “o”

Recommended approach

Using the Object Mapping

Rest-Assured supports mapping Java objects to and from JSON and XML. This means you can create a Java object and Rest-Assured will try to match all the attributes in the class with either the request or response.

To be able to use it you need to explicitly add an object mapping library in your classpath. For JSON you need to have either Jackson, Jackson2, Gson, or Johnzon in the classpath and for XML you need JAXB.

As we are sending information into the request let’s talk about first the Serialization.

Object Mapping Serialization

https://github.com/rest-assured/rest-assured/wiki/Usage#serialization

Rest-Assured will use one of the object mapping libraries you added into your classpath to bind the attributes from a Java object into the request body. We already know that we required, for this example, the name and job attributes to post an user. The Java object will look like this:

public class User {

    private String name;
    private String job;

    public User(String name, String job) {
        this.name = name;
        this.job = job
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

      public String getJob() {
        return job;
    }

    public void setJob(String job) {
        this.job = job;
    }
}
Enter fullscreen mode Exit fullscreen mode

In the test, we need first to create the User object with the data we would like to use. You can see this on line 6.

Now we simply use the user object into the body(object). It’s important to explicitly set the Content-type. You can see it in lines 9 and 10.

class PostTest {

    @Test
    void postUsingObjectMapping() {

        User user = new User("Elias", "Principal Engineer");

        given().
            contentType(ContentType.JSON).
            body(user).
        when().
            post("/users").
        then().
            statusCode(HttpStatus.SC_CREATED).
            body(
                "name", is(user.getNama()),
                "job", is(user.getJob()),
                "id", notNullValue(),
                "createdAt", notNullValue()
            );
    }
}
Enter fullscreen mode Exit fullscreen mode

Rest-Assured will do the magic when the body() method uses an object where it will try to find the required request attribute names into the object used.

The request needs the name and job attributes, as text. As the User object has the name and job attributes as String (text) RestAssured will to the automatic value match. Both combinations of attribute and value match are called Object Serialization.

Important note

You might face the following exception, which is super self-explanatory:

java.lang.IllegalStateException: Cannot serialize object because no JSON serializer found in the classpath. Please put Jackson (Databind), Gson, Johnzon, or Yasson in the classpath.

at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:490)
at org.codehaus.groovy.reflection.CachedConstructor.invoke(CachedConstructor.java:72)
at org.codehaus.groovy.runtime.callsite.ConstructorSite$ConstructorSiteNoUnwrapNoCoerce.callConstructor(ConstructorSite.java:105)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCallConstructor(CallSiteArray.java:59)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:263)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.callConstructor(AbstractCallSite.java:277)
at io.restassured.internal.mapping.ObjectMapping.serialize(ObjectMapping.groovy:160)
at io.restassured.internal.mapping.ObjectMapping$serialize.call(Unknown Source)
at org.codehaus.groovy.runtime.callsite.CallSiteArray.defaultCall(CallSiteArray.java:47)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:125)
at io.restassured.internal.RequestSpecificationImpl.body(RequestSpecificationImpl.groovy:751)
Enter fullscreen mode Exit fullscreen mode

This will happen if you don’t add any library to serialize the objects. You need to explicitly add one of the following libraries:

It might be the case you are not using one of these libraries, having a successful test execution. Some dependencies can have one of these as an internal dependency, and you can figure out it simply by running mvn dependency:tree and trying to look at one of the libraries mentioned.

Examples

In the restassured-complete-basic-example project, you can find the Simulation model, which models the request and response for the Simulations API.

The model is in use by the SimulaitonDataFactory to create the Simulation data, necessary to make the POST request using the body parameter.

The SimulationFunctionalTest has tests using different HTTP methods. You can see the createNewSimulationSuccessfully, invalidSimulations, and simulationWithDuplicatedCpf test methods using the Simulation object to POST a body using it instead of Strings or any other mentioned approach.

Discussion (0)