Testing RESTful Service in Ruby

RESTful web services are based on the Representational State Transfer (REST) architecture. In a RESTful service, payloads are in some uniform format (e.g. JSON, HTML, XML, etc), via HTTP.

I will use a sample REST service site, Thomas Bayer, for examples to test typical RESTful services: LIST, READ, CREATE, UPDATE and DELETE records.

Here’s how I write API tests:

  1. Try the request in a GUI tool (like Postman, Paw, etc), which can view request data and response data in a user-friendly way.
  2. Once request data and endpoint details have been confirmed, I will write the test in Ruby. The benefits are being more flexible, reusable and easier to run in a CI/CD pipeline.

We can use HTTP test applications such as Postman and Paw (macOS only) to invoke REST API calls. I liked the old Postman, but now I prefer to use Paw.

LIST all records

HTTP Method: GET
URL: http://thomas-bayer.com/sqlrest/CUSTOMER

In Paw

In Ruby

We use an HTTP library, such as httpclient, to send HTTP requests.

require 'httpclient'
http = HTTPClient.new

GET retrieves data by a given URL, in this case, http://thomas-bayer.com/sqlrest/CUSTOMER.

# LIST
resp = http.get("http://www.thomas-bayer.com/sqlrest/CUSTOMER")
# (optional) write response contents to file
File.open("/tmp/rest-list-customers.xml", "w").puts(resp.body)

The response (resp) has the content:

<?xml version="1.0"?>
<CUSTOMERList xmlns:xlink="http://www.w3.org/1999/xlink">
<CUSTOMER xlink:href="http://www.thomas-bayer.com/sqlrest/CUSTOMER/1/">1</CUSTOMER>
<CUSTOMER xlink:href="http://www.thomas-bayer.com/sqlrest/CUSTOMER/2/">2</CUSTOMER>
<CUSTOMER xlink:href="http://www.thomas-bayer.com/sqlrest/CUSTOMER/3/">3</CUSTOMER>
<CUSTOMER xlink:href="http://www.thomas-bayer.com/sqlrest/CUSTOMER/4/">4</CUSTOMER>
<!-- more -->
</CUSTOMERList>

Next, we parse this XML document to do verification, here I will use Ruby’s built-in REXML. Below are two assertions to verify the customer count and the first customer’s ID.

require 'rexml'xml_doc = REXML::Document.new(resp.body)
expect(xml_doc.root.elements.size).to be > 10
expect(xml_doc.root.elements.first.text).to eq("1")

READ a record

HTTP Method: GET
URL: http://thomas-bayer.com/sqlrest/CUSTOMER/3

In Paw

In Ruby

http = HTTPClient.new
customer_id = 3
get_rest_url = "http://www.thomas-bayer.com/sqlrest/CUSTOMER/#{customer_id}"
# call http request
resp = http.get(get_rest_url)

The reason I used a variable customer_id here is to make it easier to change.

The response (resp) has the content:

<?xml version="1.0"?><CUSTOMER xmlns:xlink="http://www.w3.org/1999/xlink">
<ID>3</ID>
<FIRSTNAME>Michael</FIRSTNAME>
<LASTNAME>Clancy</LASTNAME>
<STREET>542 Upland Pl.</STREET>
<CITY>San Francisco</CITY>
</CUSTOMER>

Assertion:

# verify field's value
expect(resp.body).to include("<CITY>San Francisco</CITY>")

Now that we can read records, let’s see how to create new ones.

CREATE a new record

HTTP Method: PUT
URL: http://thomas-bayer.com/sqlrest/CUSTOMER

PUT sends record data to the server, to create a new record.

My new customer request data will be:

<CUSTOMER>
<ID>66670</ID>
<FIRSTNAME>P</FIRSTNAME>
<LASTNAME>Sherman</LASTNAME>
<STREET>42 Wallaby Way</STREET>
<CITY>Sydney</CITY>
</CUSTOMER>

Note that the value in the ID field is arbitrary; just try to make yours unique.

In Paw

The example server returns a 403 (Not Authorised) error for a PUT request, but the new record was actually created successfully. This does not affect our testing.

In Ruby

http = HTTPClient.new
create_rest_url = "http://www.thomas-bayer.com/sqlrest/CUSTOMER/"
new_record_xml = <<END_OF_MESSAGE
<CUSTOMER>
<ID>66670</ID>
<FIRSTNAME>P</FIRSTNAME>
<LASTNAME>Sherman</LASTNAME>
<STREET>42 Wallaby Way</STREET>
<CITY>Sydney</CITY>
</CUSTOMER>

END_OF_MESSAGE
resp = http.put(create_rest_url, new_record_xml)
puts resp.body

Assertion with by READ-ing the record to verify that the new customer was created successfully.

customer_id = 66670
get_rest_url = "http://www.thomas-bayer.com/sqlrest/CUSTOMER/#{customer_id}"
resp = http.get(get_rest_url)
expect(resp.body).to include("<CITY>Sydney</CITY>")

UPDATE a record

HTTP Method: POST
URL: http://www.thomas-bayer.com/sqlrest/CUSTOMER/66670

Like CREATE, an XML is required as part of the request. However, because it is an UPDATE, the XML should only contain the fields that are going to be updated. i.e. if you are not changing CITY, then do not include it in the request.

Let’s say I want to update customer P Sherman’s firstname to Paul (ID: 66670). The update request data is:

<CUSTOMER>
<FIRSTNAME>Paul</FIRSTNAME>
</CUSTOMER>

and request URL contains the customer’s ID: http://www.thomas-bayer.com/sqlrest/CUSTOMER/66670

In Paw

In Ruby

To make our automated test reliable, we need to make sure the record exists first. The best way is to create a brand new record first, and then update it. (same for deletion)

http = HTTPClient.new
cid = 66670
update_rest_url = "http://www.thomas-bayer.com/sqlrest/CUSTOMER/#{cid}/"
update_xml = <<END_OF_MESSAGE
<CUSTOMER>
<FIRSTNAME>Paul</FIRSTNAME>
</CUSTOMER>

END_OF_MESSAGE
resp = http.post(update_rest_url, update_xml)
expect(resp.code).to eq(200) # OK

Assertion, use the READ script to verify the change was successful:

http = HTTPClient.new
customer_id = 66670
get_rest_url = "http://www.thomas-bayer.com/sqlrest/CUSTOMER/#{customer_id}"
resp = http.get(get_rest_url)
expect(resp.body).to include("<FIRSTNAME>Paul</FIRSTNAME>")

DELETE a record

The final operation in this tutorial is DELETE.

HTTP Method: DELETE
URL: http://www.thomas-bayer.com/sqlrest/CUSTOMER/66670

The expected response to a DELETE call is:

<resource xmlns:xlink="http://www.w3.org/1999/xlink">
<deleted>66670</deleted>
</resource>

In Paw

In Ruby

it "Delete a record" do
http = HTTPClient.new
cid = 66670 # an existing record
delete_rest_url = "http://www.thomas-bayer.com/sqlrest/CUSTOMER/#{cid}"
resp = http.delete(delete_rest_url)
expect(resp.body).to include("<deleted>66670</deleted>")
end

Assertion, verify the record was deleted successfully using READ. This time you are expecting the response to not include any field.

http = HTTPClient.new
customer_id = 66670
get_rest_url = "http://www.thomas-bayer.com/sqlrest/CUSTOMER/#{customer_id}"
resp = http.get(get_rest_url)
expect(resp.body).not_to include("<CITY>Sydney</CITY>")

In this article we have gone through the Ruby scripts for LIST, READ, CREATE, UPDATE and DELETE calls.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store