In the previous section, we wrote a number of automated tests that verified responses’ headers, but headers is just part of the picture. We haven’t actually test what really matters: the body of the response message; the actual content. And that’s what will be doing in this section, also using only HTTP Get requests.
As an example, we’ll be querying the endpoints, and these are some of the fields that are present in the body:
There are many more, but it’s enough to learn how to get one or two of those and then same principle can be applied to all other fields.
Let’s outline what we will be doing:
- Explore the Response Object. First, we will run existing test in debug mode, and we’ll inspect the response message and try to where that body is located. The entity that contains the content.
- String Manipulation and JSON Object. We’ll try to extract it so we can test it. To do that, to extract the body, we’ll try to use several ways and we’ll see that some might seem quick and easy, but they might not the best ones in the long run.
- (Un)Marshalling. We’ll then take a small step back and take a look at the concept of marshalling and unmarshalling objects. We need to understand what it is, what we can use to achieve it, how to do it in practice, and, ofc, what benefit it brings.
- Automated Mapping with a Plugin. Finally, we’ll apply this concept to our new tests and see how some extra effort results in clean and easy-to-understand tests.
Finding JSON in HTTP Response
Let’s run a test in the debug mode and inspect the object.
I will set a breakpoint right after we get a response and trigger the test in debug.
Once we hit that breakpoint, we can see in the tab Debug that there is a response variable. If we dig a few levels deep, we’ll notice several interesting things. We can see the statusline that is imported enough to have its own field right at the top of the object structure, and below – the headergroup.
Expand the headergroup. And we can see it’s an array list containing all the headers of the response:
That’s what we’ve been testing until now, but where is the body? Where is JSON? IT’s over here, in the entity object, and we’ve dealt with that entity object before.
As we can see, it’s nested deeper into the overall structure. We see that we encounter an inputStream. So there is no convenient string field that will allow us to just read the text of the body.
What we’d have to do is to write plan old Java code using a buffered reader that takes an input stream reader, read that line by line, and append it all to a string object; classic.
But we’re not going to do that. Life is way too short to write such boilerplate code. Let’s take a look at easier ways to get our JSON content.
JSON as String or JSON Object
So, now we know that the body is nested inside the entity object, which itself is nested inside the response object. So let’s call getEntity on that response object.
Hmm, we could use next the .getContent, but that returns an input stream. We could go down that road, but as we won’t, especially since the developers of the Apache HTTP library have already taken care of this for us.
What we need to do is to call EntityUtils.string method. This returns a string. Let’s print the string and see what it contains:
String jsonBody = EntityUtils.toString(response.getEntity());
In fact, if we take a peek inside the two string methods of the EntityUtils class, we can confirm that it’s actually using the low-level code that we discussed in the last paragraph. Getting an input stream and then using a stream reader to loop over the contents appended to a buffer and then return it. Lucky us that someone has already written this stuff for us.
Now, if we wanted to stop here, we could start testing this string. We could verify that the string contains the login substring, the id substring, the specific value of that id, basically, a lot of code that manipulates strings, perhaps including regular expressions.
json.substring(json.lastIndexOf("login\"")); json.contains(loginValue);
This might seem like an attractive option at first. Most people that learn how to code, learn how to manipulate strings. So it seems like the immediate solution, but experienced programmers advice against it.
Using regular expressions and lots of substring operations eventually leads to mistakes and false positives and negatives, which means that your tests will fail even when everything is actually correct or vice versa. Your test will pass because it found the right piece of substring, but it would be the wrong one.
What we need is to convert this string to some object that has structure. One of the simplest ways is to use a small and simple Java reference implementation.
Let’s add it to our Maven project. Go to Maven Repository, search for JSON, and just as before, copy/paste the XML snippet to your pom file.
Now we can create a JSON object. That’s possible thanks to the new dependency. Let’s pass our string to that JSON object.
JSONObject jsonObject = new JSONObject(jsonBody);
So what’s the structure of this JSON object? Let’s run again in debug mode and see. Let’s expand the tree iof the JSON object. It’s a simple map, containing keys and values.
We can see that they’re not in order, that’s fine; a hash map is being used, but it cannot guarantee any kind of order. This is already much better. We can query this map by a key and get the exact value we want.
Let’s define a method getValueFor that takes two parameters, the JSON object, and the key by which we want to search. And the implementation is rather simple; just get the value of whatever key we pass in and return that value.
private Object getValueFor(JSONObject jsonObject, String key){
return jsonObject.get(key);
}
Ofc, if we pass in a bad key and the map doesn’t contain that key, it will throw an exception. So ideally we will put this in a try/catch block. But we won’t be working with this method for too long so let’s leave it as it is.
Note: we are returning a type Object. Why? Because the actual value might be a string, an integer, an integer, a double, a Boolean, or something else. So we return an object and cast it down in the test ourselves.
Now fetch the value of the login field, save that to a variable, cast it down from object to string.
String loginValue = (String) getValueFor(jsonObject, "login");
And then assert. Run the test. It should pass! Make a typo, just to be saved the tests fails with the mistake. Hurray! Throws and exception, as expected.
So, we made a type right now, which means this could happen again. In fact, this could happen quite often, and we want to avoid that. Using constants can help us; to be more precise – making a public static final variable. But let’s not define it in just our test class.
Let’s create a Java object called User and define our constant there. In the main folder.
This User java object represents the JSON content that we get. So in this object here, we can go ahead and define other strings that exist in that JSON body; the login, id, email, and so on and so forth.
public class User { public static final String LOGIN = "login"; public static final String ID = "id"; }
Now let’s reference this constant in our test. This should work the same.
String loginValue = (String) getValueFor(jsonObject, LOGIN);
Let’s write another test for the id, which always has to be the same number for the same user. Test is identical; the only thing we are changing is the key we a querying, id instead of login, and the expected value. And ofc we’re casting over here to an integer, not a string.
Run the test ..and passed. We could and should create such Java objects for other kinds of responses. The user endpoint, as we just did (class User), the base endpoint object (class BaseClass) which tells us how to use the API, and for all other different responses.
And in each class, we would define the fields that are actually present in the JSON body o the response. (=mapping to Java objects). This has several benefits.
Benefits of Mapping to Java objects:
- Reduces Mistakes.
- Structures the Framework. It gives our code-base simple, yet solid structure that is easier to maintain than just hard-coding things into test scripts.
- Follows the Object-Oriented Approach.
Now we could stop here and just carry on as is. I mean, start writing dozens of tests that use this one small JSON library and this really simple getter method. This however, is workable when all of your response bodies are flat. JSON, XML and other formats can have nested structures, meaning field within a field within a field, and in reality, you get that a lot. And our API under test is actually no exception.
We might not get a nice, simple string, but another map that needs unpacking, and in the case of a deeper nesting, even more unpacking. Basically, a map, within a map, within a map. So how do we overcome this challenge?
Well, the first thing to realize is that if you come a programming problem, chances are that a solution already exists and you don’t have to reinvent the wheel.
In our case, this unpacking of a JSON object has a well-defined name. It’s called marshalling and unmarshalling and that’s what we’re going to look at next.
(Un)Marshalling
Let’s take a look about what Wikipedia has to say about marshalling.
In computer science, marshalling is the process of transforming the memory representation of an object to a data format suitable for storage or transmission, and it is typically used when data must be moved between different parts of a computer program, or from one program to another. (also called serialization
In our case, it basically means converting Java objects into a readable file such as JSON and unmarshalling is the reverse process; converting an XML or JSON (or other format) file to a Java object in memory.
Might be considered as a simplified definition, but it’s good enough to do our job and test the Web API. Anyway, we’re not interested in marshalling, but we are very interested in proper unmarshalling.
What we’ve been doing until now is a kind of very rudimentary unmarshalling, but why reinvent the wheel? For XML, there’s a standard software framework that does the conversion back and forth and it is called JAXB.
But we’re dealing with JSON, so what options do we have for JSON? Well, there is no single all-powerful library, but two that are widely used are Jackson and Gson from Google. There’s no reason to claim the one is better than the other so in this paragraph, we’ll stick to Jackson.
With Jackson, we’ll first learn to convert simple JSON objects, and after, we’ll move to the more complex ones that have nesting.
Unmarshalling a Simple JSON Object
We need to import the Jackson library that will do the magic for us.
Go to Maven Repository, search for Jackson, and get the Databind. The Databind contains the Jackson core, anyway, and get the XML snippet as you did before. Again, just pick the highest version and declare it in you pom file. Let Maven import it.
Alright. Now, if we want to convert JSON to a Java object, we need to create one, and it just so happened that we’ve already started doing it;. This User object is just what we need, but instead of a collection of constants, let’s define them as fields and create getter methods for them. There, a plain old Java object. It couldn’t be simpler:
private String login; private int id; public String getLogin() { return login; } public int getId() { return id; }
Now, let’s declare a method unmarshall; it will take two parameters. The response; we always want to unmarshall from the response, and the user class. With this, we’re saying that whatever JSON we find there, we expect it to contain user stuff. So we can map that to the User class.
User user = unmarshall(response, User.class);
Alt+Enter and create the method.
private User unmarshall(CloseableHttpResponse response, Class<User> clazz) { }
As you can see, the method is auto created to return the user, and the second parameter is of type class user. So this involves Java Generics, and we’ll get back to that in a moment.
So we have to do two things; get the JSON body and unmarshall.
First step is easy. We already know how to do that with EntityUtils. Then, Jackson enters the stage. Type the ObjectMapper – the main object that does the unmarshalling work. Call on it method readValue – takes two parameters; the first parameter can be a lot of things, but in our case, it’s just a string that contains structured JSON, and the second parameter takes a class <T>, which means a generic class or any kind of class, including our own User class.
private User unmarshall(CloseableHttpResponse response, Class<User> clazz) throws IOException { String jsonBody = EntityUtils.toString(response.getEntity()); return new ObjectMapper().readValue(jsonBody, clazz); }
Now, all that’s left is to write an assertion. When we get the login of the user objects, that we kind of populated with JSON content, it should be equal to the expected value.
Assert.assertEquals(user.getLogin(), "andrejss88");
Test failed. Message: UnrecognizedPropertyException: Unrecognized field “node_id”
We have two known properties: login & id. Well, let’s take a look at the entire user JSON body in the browser. Yep, that’s a lot of fields and node_id is one of them, but if we take another look at our user class, indeed, we have defined two fields only, and the exception is telling us that Jackson has no idea what to do with the node_id. Jackson finds it and tries to do something with it, looks at our user class, doesn’t find that field, and gives up. That’s actually sensible behavior.
We can solve this in two ways. First, obviously, define all the fields in our user class. In that case, Jackson will stop being confused, and that’s what we should do if we want to test all possible fields. But there is also another solution where we don’t need to type out all those fields.
Before the readValue method, let’s chain a method called configure, and in that method, input FAIL_ON_UNKNOWN_PROPERTIES as the first parameter, and false as the second.
return new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.readValue(jsonBody, clazz);
We’re basically instructing Jackson to not fail and just keep going if it doesn’t find a matching field in our user class. Run the test again.
Test passed.
Now let’s try another test to verify the id. Pass.
As you can see, we have to invest some effort into learning how to use the Jackson library, but this allows us to write valid tests so short that they fit in five lines of code or less.
Last thing to do – move the unmarshall method to the utils class so we can reuse it.
Unmarshalling to Any Class with Generics
Now, let’s write one more test. Remember that non-existing endpoint test that returns a 404 – Not Found? Well, that response actually has a body as well and it has two fields: not found and a documentation link.
Let’s test for the first field. Define a new class e.g. NotFound, and define just one field that we care about in this demo – the message.
Create a new test (copy last one) and change the types from User to NotFound. Now, IDE is complaining that we try to fit a square into a round hole. Meaning, we’re passing a NotFound class, but we defined the second parameter for unmarshall method as of type User. So now what? Do I define 20 different unmarshall methods for 20 different classes? That’s possible but not good. Instead, we’ll make the method be generic.
public static <T> T unmarshallGeneric(CloseableHttpResponse response, Class<T> clazz) throws IOException
This basically means, whatever you pass in, I will give you back the same thing. You pass in a user/a NotFound class /an unicorn, I give you back a user/a NotFound class/an unicorn.
Go back to our user test, change the methods from unmarshall to unmarshallGeneric, and validate – it still works! Good. Now go to the current test and change. Compiles now!
Finish it with an assert and run it. Excellent! The complexity of what we are doing has increased quite a bit, bud do note one thing. The tests are still short and very easy to read.
Unmarshalling Nested JSON
Let’s deal with unmarshalling nested JSON. We can use the rate limit endpoint.
As you can see from the indentation, there is nested data. One is under core and the other under search.
Let’s focus on the core limit field.
Define the rate limit class; nothing special here, coreLimit and searchLimit as well as their getters.
Now create the test, nothing new: send a get request, receive a response, pass that response into the Jackson method that we defined, and we’re telling it to try to unmarshall JSON to our rate_limit class. That’s the main difference. We save that to a variable and we have a simple assertion at the end.
Test Failed. java.lang.AssertionError: expected [60] but found [0]
Which is not surprise. We never told Jackson anything about nested fields, so it tried to find something at the top level, didn’t find anything, so it returned null.
Let’s get back to our rate_limit class and fix that. We need to implement the method. It should return void, and let’s call it unmarshallNested, but you can give it any name that makes sense. This method should take one parameter, a map of strings and objects.
Next, we declare an annotation @JsonProperty and we call it resources as well. This is the important bit. This tells Jackson that it should look for such a field in JSON at the top level. That’s what we saw in the browser.
Now, when we call a get on our resources, we specify “core” and this should find all of the nested JSON data inside the core. So let’s save that to a map of strings and integers. Strings are gonna be the field names limit, remaining and reset, and the integers are going to be the corresponding values. We also need to explicitly tell Java that we do want to cast from object to integer.
@SuppressWarnings("unchecked")
@JsonProperty("resources")
private void unmarshallNested(Map<String, Object> resources){
Map<String, Integer> core = (Map<String, Integer>) resources.get("core");
coreLimit = core.get("limit");
Map<String, String> search = (Map<String, String>) resources.get("search");
searchLimit = String.valueOf(search.get("limit"));
}
Passed.
We gave Jackson the instructions to dig inside the JSON body and get the right field. We also made an identical one for the second field – for the limit to be a string. We returned a map o Strings, not Strings and Integers. And here lies the reason why we declare the type Object in the method signature. JSON could contain strings, integers, Booleans and other types. That is why you want to declare type Object at the top and then, inside the method, decide if you want to cast it down to either String, Integer or something else.
POJO Generation with a Plugin
Defining a custom class takes a while. The rate_limit class is small enough. The user class already has about 20 different fields, which means a lot of typing. Ok, 20 is still not too bad, but then you get to the repository endpoint, which has dozens and dozens of fields and some of them are nested. So how much time are we gonna spend typing all of this?
The answer is, we don’t. Remember, if you come across a problem there is a high chance that someone has already thought of a solution, and in our case the solution is called jsonschema2pojo. POJO stands for Plain Old Java Objects. This does the hard work for us and saves us a lot of time.
If you’re not using Maven, all you have to do is space in your JSON on this website: www.jsonschema2pojo.org, give your class a name, select Java, and select the annotation style, and then click either Preview or download a zip, and you get your classes generated for you.
If you’re using Maven, you can use the plugin version. Define it as below:
Place your json file into schema folder. Then: View -> Tool Windows -> Maven Projects -> right-click on the jsonschema2pojo -> Run Maven Build and find the generated files in the com.github.entities folder!
The generated classes contain a few extra annotations that we haven’t talked about, but you can look those up in the Jackson public documentation whenever you like.
Summary!
- Inspected the HTTP response and tried to understand where JSON is hidden. We saw that it’s not exactly conveniently packaged inside a string, but using the existing util methods of Apache HTTP Client, we could extract it into a string.
- Considered and tried out several ways to test that JSON. First thought was to just work with the string as a whole using substrings, but we rejected that option straightaway. Then we gave our string some structure by using a JSON object, much better, but still not ideal, at least when you have nested JSONs. We then moved on using the Jackson library that specializes in unmarshalling, converting JSON to a Java object. What we have seen is only the tip of the iceberg of what Jackson can do. It is a mature piece of software and has many more features.
- Last but not least, we looked at the plugin called jsonschema2pojo, to generate Java classes for us. It helps a lot when you have big JSON bodies that would take you hours to type, and it doesn’t help just once. The API that you are testing evolves so the JSON structure would also change and evolve, meaning that you would make use of this plugin many times. If you think about it, we’re using an automation tool that helps us to do other kinds of automation, which is a fun fact to think about.