REST Assured in Kotlin

REST Assured is a library for testing HTTP/REST based services on the JVM and version 4.1.0 made this experience even better for Kotlin developers by introducing a new Kotlin API. This blog-post will briefly introduce the new API and hopefully convince you that it’s preferable to the Java API if you’re using Kotlin. But first let’s look at a simple example.

Example Resource

We’re going to keep things simple and just assume we have a webserver running at http://localhost:8080/ that upon receiving a GET to `/users?deleted=false` will return the following JSON response (copied from https://jsonplaceholder.typicode.com/users):

[
  {
    "id": 1,
    "name": "Leanne Graham",
    "username": "Bret",
    "email": "Sincere@april.biz",
    "address": {
      "street": "Kulas Light",
      "suite": "Apt. 556",
      "city": "Gwenborough",
      "zipcode": "92998-3874",
      "geo": {
        "lat": "-37.3159",
        "lng": "81.1496"
      }
    },
    "phone": "1-770-736-8031 x56442",
    "website": "hildegard.org",
    "company": {
      "name": "Romaguera-Crona",
      "catchPhrase": "Multi-layered client-server neural-net",
      "bs": "harness real-time e-markets"
    }
  },
  {
    "id": 2,
    "name": "Ervin Howell",
    "username": "Antonette",
    "email": "Shanna@melissa.tv",
    "address": {
      "street": "Victor Plains",
      "suite": "Suite 879",
      "city": "Wisokyburgh",
      "zipcode": "90566-7771",
      "geo": {
        "lat": "-43.9509",
        "lng": "-34.4618"
      }
    },
    "phone": "010-692-6593 x09125",
    "website": "anastasia.net",
    "company": {
      "name": "Deckow-Crist",
      "catchPhrase": "Proactive didactic contingency",
      "bs": "synergize scalable supply-chains"
    }
  },
  {
    "id": 3,
    "name": "Clementine Bauch",
    "username": "Samantha",
    "email": "Nathan@yesenia.net",
    "address": {
      "street": "Douglas Extension",
      "suite": "Suite 847",
      "city": "McKenziehaven",
      "zipcode": "59590-4157",
      "geo": {
        "lat": "-68.6102",
        "lng": "-47.0653"
      }
    },
    "phone": "1-463-123-4447",
    "website": "ramiro.info",
    "company": {
      "name": "Romaguera-Jacobson",
      "catchPhrase": "Face to face bifurcated interface",
      "bs": "e-enable strategic applications"
    }
  }
]

Validation with the Java API

Now let’s suppose we want to validate a couple of things:

  1. That the status code is equal to 200
  2. That there are 3 users in the list
  3. That one of the users has a name of “Ervin Howell”
  4. The user with username “Samantha” is employed at “Romaguera-Jacobson”

and then we’d like to return the cities of all users. We can do this quite nicely with the Java API:

List cities = 	
given().
		queryParam("deleted", false).
when().
		get("/users").
then().
		statusCode(200).
		body("size()", is(3)).
		body("name.any { it == 'Ervin Howell' }", is(true)).
		body("find { user -> user.username == 'Antonette' }.company.name", equalTo("Romaguera-Jacobson")).
extract().
		path("address.city");

While this works fine there are a few annoyances:

Reporting

The first one has to do with the way reporting of validation errors works. Since REST Assured can’t know which body statement that “terminates” the operation it can only perform, and thus report, validations errors one by one. For example if body("size()", is(3)) fails REST Assured cannot continue and check the two remaining statements (body("name.any { it == 'Ervin Howell' }", is(true)) and body("find { user -> user.username == 'Antonette' }.company.name", equalTo("Romaguera-Jacobson"))) since it doesn’t know about these statements ahead of time. You’d have to fix the first statement and then re-run the test to know whether or not the subsequent statements pass or not. For this reason REST Assured has a concept called “multi-body expectations” which let’s you define multiple expectations within a single body statement:

List cities = 	
given().
		queryParam("deleted", false).
when().
		get("/users").
then().
		statusCode(200).
		body(
			 "size()", is(3),
			 "name.any { it == 'Ervin Howell' }", is(true),
			 "find { user -> user.username == 'Antonette' }.company.name", equalTo("Romaguera-Jacobson")
		).
extract().
		path("address.city");

This fixes the problem described above, now all body expectations will be validated in one go and all errors will be reported at once. However if any of the statements preceding the call to body were to fail (statusCode(200) in this case) then body would not be executed since the test has already failed. In a trivial example like this it might not matter all that much, but image a different scenario where you’re also expecting contentType and headers then you can probably tell this is not optimal.

Formatting

Another annoying thing is that the Java DSL doesn’t work well with the default formatting settings in your IDE. If I press cmd+option+l to format the code in Intellij it would look like this:

List cities =
                given().
                        queryParam("deleted", false).
                        when().
                        get("/users").
                        then().
                        statusCode(200).
                        body("size()", is(3)).
                        body("name.any { it == 'Ervin Howell' }", is(true)).
                        body("find { user -> user.username == 'Antonette' }.company.name", equalTo("Romaguera-Jacobson")).
                        extract().
                        path("address.city");


Which is much less readable. To work around this I often select the code, press cmd+option+t and then F to disable formatting. Intellij will then insert hints that prevents the code from being formatted in the future:

// @formatter:off
List cities =
given().
		queryParam("deleted", false).
when().
		get("/users").
then().
		statusCode(200).
		body("size()", is(3)).
		body("name.any { it == 'Ervin Howell' }", is(true)).
		body("find { user -> user.username == 'Antonette' }.company.name", equalTo("Romaguera-Jacobson")).
extract().
		path("address.city");
// @formatter:on

Validation with the Kotlin API

By adding the Kotlin Extensions Module to the classpath you can start using the API by importing Given from the io.restassured.module.kotlin.extensions package. The test can then be rewritten as:

val cities : List =
Given {
	queryParam("deleted", false)
} When {
	get("/users")
} Then {
	statusCode(200)
	body("size()", is(3))
	body("name.any { it == 'Ervin Howell' }", is(true))
	body("find { user -> user.username == 'Antonette' }.company.name", equalTo("Romaguera-Jacobson"))
} Extract {
	path("address.city")
}

It looks similar enough to the Java API but it solves both issues presented earlier. Since the API uses Kotlin’s type-safe builders the formatting problem goes away since Intellij natively understands and formats these builders in a nice way. Also, since all expectations (statusCode and body) are defined inside the Then block, REST Assured can run through all of them in the same go and report all errors at once. This can save you quite a lot of time since you typically use REST Assured for integration testing and the environment (such as Spring) may take some time to boot.

Why capital letters?

You may be wondering why the methods are starting with a capital letter (Given, When, Then etc)? The reason is that when is a reserved keyword in Kotlin and thus it has to be escaped when using it in code (i.e. `when`). To avoid this, the When extension function was introduced. To make things consistent the other methods was thus also named with the first letter capitalized. I’d love the hear some comments on this decision though, does it make sense or not?

Summary

As you’ve seen, the Kotlin API can help to solve two common annoyances that you may experience with the Java API. For this reason, the Kotlin API is recommended for Kotlin users. Any feedback, for example on the naming of methods, is highly appreciated. Thanks for reading!

2 thoughts on “REST Assured in Kotlin

  1. how to add keystore and truststore jks in RestAssured ? ” there is my code but got BadPaddingException, I need help for this issue thanks a lot
    detail
    Caused by: javax.crypto.BadPaddingException: Error finalising cipher data: pad block corrupted
    at org.bouncycastle.jcajce.provider.BaseCipher.engineDoFinal(Unknown Source)
    at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202)
    at java.base/sun.security.pkcs12.PKCS12KeyStore.lambda$engineGetKey$0(PKCS12KeyStore.java:406)
    at java.base/sun.security.pkcs12.PKCS12KeyStore$RetryWithZero.run(PKCS12KeyStore.java:295)
    at java.base/sun.security.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:400)
    at java.base/sun.security.util.KeyStoreDelegator.engineGetKey(KeyStoreDelegator.java:90)
    at java.base/java.security.KeyStore.getKey(KeyStore.java:1057)
    at java.base/sun.security.ssl.SunX509KeyManagerImpl.(SunX509KeyManagerImpl.java:145)
    at java.base/sun.security.ssl.KeyManagerFactoryImpl$SunX509.engineInit(KeyManagerFactoryImpl.java:70)
    Code is
    private RestAssured restAssured;
    restAssured; restAssured.config = RestAssured.config().sslConfig(new SSLConfig()
    .trustStore(“truststore.jks”, “password”)
    .keyStore(“keystore.jks”,”password”)
    .keystoreType(“jks”)
    .trustStoreType(“jks”)
    );

Leave a Reply

Your email address will not be published. Required fields are marked *