API Automation with Groovy and Spock

API Automation with Groovy and Spock

API automation can be a tricky part in software testing. There are quite a few options to choose from when it comes to automating APIs. We can choose to use existing tools like Postman, SOAP UI, even Jmeter or we can choose to develop a whole project using a programming language and preferably with a framework tool. The first option can prove to be cost effective in terms of time investment but this option does not provide us much flexibility in implementing an extensive test suite. On the other hand, the latter option gives us independence to design our automation project to best serve our purpose. We can maintain our desired data structure, we can build custom reports as per our needs, we can use a version controlling system, integrate our project with a CI/CD pipeline and many more. Although, in this case we need to put in more effort and more time.

In this article we will discuss how to automate REST API using Spock framework of Groovy.

From official groovy site:

Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming.

One of the core features of Groovy is its scripting capability which enables us to use it in our API automation. Spock framework utilizes this property very efficiently and provides options of implementing test cases in Behavior Driven Testing and Data Driven Testing approaches.

From spock documentation site:

Spock is a testing and specification framework for Java and Groovy applications. What makes it stand out from the crowd is its beautiful and highly expressive specification language. Thanks to its JUnit runner, Spock is compatible with most IDEs, build tools, and continuous integration servers. Spock is inspired from JUnit, RSpec, jMock, Mockito, Groovy, Scala, Vulcans, and other fascinating life forms.

Implementing REST API Client:

Groovy offers a default package to send request and fetch response from an API. This is the RESTClient class in groovy implementation which can be invoked in our project like this:

import groovyx.net.http.RESTClient

This class gives us option to implement APIs of all the method types (POST, GET etc.). The constructor of this class takes one single parameter which is the base URL or the host URL. We can initialize this class like this:

static def restClient = new RESTClient(API_BASE_URL)

If we need to change this base URL at any point in our project we can use setUri() method of RESTClient class like this:

restClient.setUri(ANOTHER_BASE_URL)

In this article we will discuss one POST and one GET method implementation. Lets say, we have a user-login API which supports POST method and a get-user-info API which supports GET method. Our APIs support content type JSON for request and response.

POST API Client:

static def loginRequest(String userName, String password) {
    def response
    def request = [
            username: userName,
            password: password              
    ]

    def correlationHeader = new HeaderBuilder()
        .addAppType("Android")
        .addAppCode("01")
        .build()

    response = restClient.post(
        path: "/api/user-login",
        requestContentType: "application/json",
        headers: correlationHeader,
        body: request
    )

    return response
}

GET API Client:

static def getUserInfoRequest(String userName) {
    def response
    def request = [
            username: userName            
    ]

    response = restClient.get(
        path: "/api/get-user-info",
        requestContentType: "application/json",
        query: request
    )

    return response
}

The patterns are almost same for POST and GET method apart from that in post method we need to map our request to key body whereas in get method the key is query. In post method we have a header builder which is passed into request headers. We can implement this header builder using simple map data structure of Java. RESTClient library automatically concatenates the path provided with the base url set in the constructor or with the url set in setUri method.

Implementing Test Cases and Invoking the API Implementations:

In implementing test cases for our automation project we will utilize the scripting property of groovy. It means that we will implement our test cases and validate them in one single class file. Spock allows us to implement the test cases in separate methods. These methods are called Feature Methods. A huge benefit of these methods are that we can use our test case titles as the method name just like we declare a string in java. Before writing our test cases, we need to add the spock dependency to our project which can be done like this:

import spock.lang.*

The single class file that we talked about which contains our test case is a groovy class (also called a spec class) which is extended from the spock package spock.lang.Specification. A sample class definition can be like this:

class APITestSpec extends Specification {}

This extension allows us to write our test cases in BDT and DDT formats. By default this specification class contains some methods which are called Fixture Methods. These methods are identical to the before, after methods of JUnit.

There are four fixture methods in spock framework.

def setupSpec() {}    // runs once -  before the first feature method
def setup() {}        // runs before every feature method
def cleanup() {}      // runs after every feature method
def cleanupSpec() {}  // runs once -  after the last feature method

We can use the setupSpec method to initialize necessary classes or global and shared variables which will be used throughout our whole spec class. For example, if we want to add serial numbers to our test cases, we can initialize the corresponding variable here. The setup method is like beforeClass method of JUnit where we can initialize necessary items we need to execute before running each test cases. For example, in setup method we can increment the test case count variable initialized in our setupSpec method.

Test Case in BDT:

The main features of behavior driven testing is the inclusion of GWT (Given, When, Then) format. In our test cases we have pre-condition(s), execution and expected and actual results which we can map with Given, When and Then respectively. Given block contains our precondition, When block contains the execution hence in our case the API call and Then block intercepts the API response and validates the actual result with our expected results. If we have multiple pre-conditions then we can map those using And tag with the Given clause.

Lets check out how we can implement test cases to invoke the user-login and get-user-info APIs we have written before.

def "User Login"() {
        given: "a registered user"
        String userName = "myUserName"
        String password = "myPassword"

        when: "we attempt to login the registered user"
        def response = RequestHelper.loginRequest(userName, password)

        then: "we receive a response"
        response.data.userId != null && response.responseBase.statusCode == 200
}

When we retrieve the response returned from our API, we need to retrieve the body using .data extension. For example if we want to fetch more fields from the response body, we can do it like this:

String userPhone = response.data.phoneNumber
String userAddress = response.data.address

Header data and other information like status code lie in .responseBase of the response. The header values are returned in a map based structure. So, we need to retrieve the headers like this:

response.responseBase.headergroup.headers

and parse them accordingly in a map structure.

Test Case in DDT:

Data driven testing means we have a data model associated with our test case and we have some parameters mentioned in our test case which are replaced by the values specified in the data model. Spock offers another clause named Where with the GWT structure which contains our test data. In our example test case, we mentioned our user name and password in the given clause. In DDT format we will put them in where block and replace them with appropriate variables in our test case. For data driven testing, we need to add an annotation Unroll before our test case to specify our DDT approach. This annotation is a part of spock.lang package.

import spock.lang.Unroll

Now, lets rewrite our test from before in DDT approach:

@Unroll
def "#testCaseCount User Login for #userName user"() {
        given: "a registered user"

        when: "we attempt to login the registered user"
        def response = RequestHelper.loginRequest(userName, password)

        then: "we receive a response"
        response.data.userId != null && response.responseBase.statusCode == statusCode 

        where: "we have a set of parameters"
        userName | password | statusCode | testCaseCount
        "myUserName" | "myPassword" | 200 | count
}

The first line of where block contains the name of the supplied parameters which are separated by a | character. Following line contains corresponding parameter values, also separated by a | character. We previously mentioned adding a counter for test cases in our setup method. We can add this variable in our where block and use the variable with # prefix in our test case name.

If we want to add more test data, we can just simply add them in new lines in the where block like this:

where: "we have a set of parameters"
userName | password | statusCode | testCaseCount
"myUserName" | "myPassword" | 200 | count
"myWrongUserName" | "myWrongPassword" | 401 | (count + 1)
"myUserName2" | "myPassword2" | 200 | (count + 2)

This will make the same test case to run the number of times equal to number of data provided.

If we need to store data and use them in further cases, we can implement model classes with our desired data structures. We just need to initialize these models in our setupSpec method and we are good to go.

Lets wrap up with comparing the pros and cons of API automation with Groovy and Spock:

Pros:

  1. Allows to write test cases in behavior driven and data driven approach.
  2. Using Groovy allows us to design our project with OOP concept.
  3. We can use Logback to log our requests and responses for better troubleshooting.
  4. We have the option to integrate our project with a CI/CD pipeline.
  5. We can implement customized reporting with our project.
  6. Can be very beneficial to implement regression or sanity test suite.

Cons:

  1. Need to write all the cases in one single spec class so code management can be a little bit tough. For example, if we have 100 test cases with an average of 10 lines of code per case, total number of lines in our spec class will be 1000 which is really huge for one single class file.
  2. Compile time of groovy is much higher which means running test cases can take a long time including the compilation time. Using a Linux machine or a CI/CD pipeline can mitigate this issue.
  3. Cannot extend one spec class from another or initiate object of one spec class in another spec class.
  4. Development time is considerably higher than that of building scripts in Jmeter or Postman.

Comparing the pros and cons, we can come to a conclusion that if we can manage the time and necessary resources in our Software Testing Life Cycle (STLC) and we are looking forward to building a strong automation suite, a Groovy-Spock project can be a very effective reinforcement in our testing arsenal.

In one of our next articles we will discuss how to add Logback and benefits of adding Gradle to our automation project.

A sample API automation project can be found here: api-automation-with-spock

Resources: