How to build an automated API test framework - Part 2

We continue with our API test framework, focusing on payloads and new tests

How to build an automated API test framework - Part 2
Scaffolding sprawling up the side of a large building

In the previous post on creating an automated api framework we created a framework and a series of automated checks that will run some basic GET request checks against restful-booker. If you haven't gone through the previous post I would recommend you do so before you continue.

...Welcome back! So let's recap on what we've created so far:

An outline of the API testing framework in it’s current state
An outline of the API testing framework in it’s current state
  • spec stores our automated checks
  • api a library of API endpoints we use in spec to communication with restful-booker
  • gemfile / Rakefile manages the running of our framework and it's dependencies

We left the previous post with three checks focused on GET requests but now we want to expand it to cover more than just GET requests. So let's look into how we can extend the framework to create and send a POST request.

Payloads and the builder pattern

Traditionally POST requests require some sort of data that we want to be consumed and actioned upon by our web service / application. For restful-booker it wants a JSON payload in the POST request to /booking so we need to create the ability to generate payloads, which we can do using the builder pattern model.

Erm, What is the builder pattern?

@FriendlyTester has a great blog post on what the builder pattern is and it's context to data creation, which is what we'll be using the pattern for. I would suggest giving it a read before continuing.

Restful-booker takes a 'booking' payload (naturally), so first create a new folder within the root of your project named payloads and within that folder create a new file named booking_payload.rb and paste the following in:

class BookingPayload

  attr_accessor :firstname, :lastname, :totalprice, :depositpaid, :checkin, :checkout, :additionalneeds

  def initialize(&block)
    instance_eval &block if block_given?
  end

  def toJson
    return "{
        \"firstname\": \"#{firstname}\",
        \"lastname\": \"#{lastname}\",
        \"totalprice\": #{totalprice},
        \"depositpaid\": #{depositpaid},
        \"bookingdates\": {
            \"checkin\": \"#{checkin}\",
            \"checkout\": \"#{checkout}\"
        },
        \"additionalneeds\": \"#{additionalneeds}\"
    }"
  end

end

So how does this builder work?

  1. The attr_accessor in the class is a shortcut method that allows you to store a variable of say, for example, firstname and create getter and setter methods for it to allow you to update at any point in our checks
  2. The intialize method is where the magic happens. It allows us to instantiate a new BookingPayload object and the &block allows to set the values we want to set for that object
  3. Finally toJson takes the values we stored in the payload and generates a stringified version of the booking JSON that we will use to add to our request

And that's it! So let's create that POST /booking check.

Let’s create some checks!

Since we are sending a POST request to the web service we need to create a new method to create the POST request. So we add the following:

def create_booking(payload, content_type)
    begin
      return RestClient.post 'http://localhost:3001/booking', payload, :accept => :json, :content_type => content_type
    rescue => e
      return e.response
    end
end

Notice how we now add a payload along with additional headers around accept and content_type.

Next, add a link to your new payload builder in spec_helper.rb by adding the following into the file:

require './payloads/booking_payload'

And finally with the booking payload class available to use we can add our new check-in integration_spec.rb

it('POST /booking should return a 200') do
    payload = BookingPayload.new do
      self.firstname = 'Sally'
      self.lastname = 'Jenkins'
      self.totalprice = 111
      self.depositpaid = true
      self.checkin = '11-11-2010'
      self.checkout = '12-11-2010'
      self.additionalneeds = 'Breakfast'
    end

    response = Booking.create_booking(payload.toJson, :json)

    expect(response.code).to be(200)
  end

What is happening here is that in addition to the request and assertion in the check we are creating a BookingPayload specific to the check by instantiating a new BookingPayload and passing values that we want to be added to the payload. We then call payload.toJson when sending the request to create the stringified JSON object that create_booking adds to the request and creates the booking and returns a 200.

Conclusion

So in summary what we've done is:

An outline of the framework in it’s completed state at the end of this post
An outline of the framework in it’s completed state at the end of this post
  1. Extended our framework to now include a library of payload builders that can be shared across any checks or requests we want to make (For example BookingPayload can be used for both POST and PUT requests)
  2. Also extended our API library to now include both GET and POST HTTP requests

For the final post in the series, we will look at combining multiple requests in a check for more complex scenarios.