The best way to add a Request Body to a POST request using Rest-Assured
Tags: testing, microservices
Published: 2021-12-08 11:54:10

# 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k21pybvtzvx775kddkta.png)

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.

```java
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()
            );
    }
}
```

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](https://github.com/rest-assured/rest-assured/wiki/Usage#create-json-from-a-hashmap) 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:

```json
{ "name": "Elias", "job": "Principal Engineer" }
```

```java

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()
            );
    }
}
```

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:

```java
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;
    }
}
```

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.

```java
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()
            );
    }
}
```

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)
```

This will happen if you don’t add any library to serialize the objects. You need to explicitly add one of the following libraries:
* [Jackson (databind)](https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind)
* [Gson](https://mvnrepository.com/artifact/com.google.code.gson/gson)
* [Johnzon](https://mvnrepository.com/artifact/org.apache.johnzon/johnzon-core)

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](https://github.com/eliasnogueira/restassured-complete-basic-example) project, you can find the [Simulation](https://github.com/eliasnogueira/restassured-complete-basic-example/tree/master/src/main/java/com/eliasnogueira/credit/model) model, which models the request and response for the Simulations API.

The model is in use by the [SimulaitonDataFactory](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/main/java/com/eliasnogueira/credit/data/factory/SimulationDataFactory.java) to create the `Simulation` data, necessary to make the `POST` request using the body parameter.

The [SimulationFunctionalTest](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/master/src/test/java/com/eliasnogueira/credit/simulations/SimulationsFunctionalTest.java) has tests using different HTTP methods. You can see the [createNewSimulationSuccessfully](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/3cf33c71b80a8508cae47eb7bf63e933d62c1567/src/test/java/com/eliasnogueira/credit/simulations/SimulationsFunctionalTest.java#L130-L147), [invalidSimulations](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/3cf33c71b80a8508cae47eb7bf63e933d62c1567/src/test/java/com/eliasnogueira/credit/simulations/SimulationsFunctionalTest.java#L149-L162), and [simulationWithDuplicatedCpf](https://github.com/eliasnogueira/restassured-complete-basic-example/blob/3cf33c71b80a8508cae47eb7bf63e933d62c1567/src/test/java/com/eliasnogueira/credit/simulations/SimulationsFunctionalTest.java#L164-L177) test methods using the `Simulation` object to `POST` a body using it instead of `Strings` or any other mentioned approach.