An introduction to REST API testing in Go with Resty

Just over a year ago, I was working in a team that, among other things, was responsible for developing and running some microservices that delivered data to a front end. It turned out those services were written in Go, a programming language that I had vaguely heard of before but never worked with.

However, as the tester and automation engineer in that team, I was tasked with writing some automated checks that could be made part of the build pipeline, so I decided that it would be a good opportunity to pick up some new skills and tools and write those checks in the same language. Now, Go is a relatively young language, first appearing in 2009. This also meant that the tool set around Go isn’t as mature as in other languages like Java or C#. Despite that, I was able to get these tests up and running fairly quickly.

Recently, I stumbled upon Go again (can’t really remember when and where, not that it’s important) and thought it would be a good idea to revisit the language. In this blog post, I’d like to show you some examples of how to write and run tests against a REST API in Go. The API I’m using for this is once again the Zippopotam.us API.

Now, Go has support for testing built-in, so unlike languages like Java or C#, you don’t need to add a unit testing framework to your project. All you need to do is import the testing library in your code, have your Go file name end with _test.go, write a method with a name starting with Test, run

go test

and you’re good to go. For setting up requests and capturing, parsing and checking the response, I found the Resty library to be very useful. It’s pretty similar to the requests library in Python, or RestSharp for C#. Here’s what a first test, checking the response status code for a correctly formatted GET requests to our API, looks like in Go with Resty:

func Test_GetUs90210_StatusCodeShouldEqual200(t *testing.T) {

	client := resty.New()

	resp, _ := client.R().Get("http://api.zippopotam.us/us/90210")

	if resp.StatusCode() != 200 {
		t.Errorf("Unexpected status code, expected %d, got %d instead", 200, resp.StatusCode())
	}
}

Note that I’m not going to explain all the nuts and bolts of Go and its syntax here. If you’re interested to learn more, I’d recommend this book or taking the Tour of Go.

If we run this test using the go test command, you’ll see that it passes:

Each test method takes an argument of type T (from the testing library), which is used to manage test state. What might strike you as odd (it did for me) is that there’s no assertion in this test like you would expect if you’re familiar with unit testing frameworks like JUnit, NUnit or pytest. The reason is simple: they don’t exist in the Go testing library. The people behind Go have their reasons for it, but this is something I don’t really like. I prefer using assertions, because they make my test code easier to read, plus it saves me from writing all these if-then-else statements myself.

Fortunately, other people thought the same, so there are third party libraries available that let you write assertions. I chose to use Testify, because it not only provides assertions, it also allows you to create test suites and use setup and teardown methods in a way similar to other languages. Here’s what the same test looks like, but now using an assertion provided by Testify:

func Test_GetUs90210_StatusCodeShouldEqual200(t *testing.T) {

	client := resty.New()

	resp, _ := client.R().Get("http://api.zippopotam.us/us/90210")

	assert.Equal(t, 200, resp.StatusCode())
}

Much better, in my opinion. Now, let’s see if we can also check a response header value:

func Test_GetUs90210_ContentTypeShouldEqualApplicationJson(t *testing.T) {

	client := resty.New()

	resp, _ := client.R().Get("http://api.zippopotam.us/us/90210")

	assert.Equal(t, "application/json", resp.Header().Get("Content-Type"))
}

That is pretty straightforward. Next, how about extracting and checking a response body value? The easiest way to do that in Go is to unmarshal (deserialize) the response body into a struct (a data structure in Go). If we want to do that, we first have to define the struct like this:

type LocationResponse struct {
	Country string `json:"country"`
}

This defines a struct LocationResponse with a single element Country. The json:”country” tag tells Go that it should populate this element with the value of the country element in the JSON response body. We don’t have to worry about the other elements in the response, those simply will not be mapped (unless you need them in a check, then you’ll need to add them to the struct, too).

Now, we can write a test that maps the response body to a struct of type LocationResponse and then check the value that has been assigned to the Country element:

func Test_GetUs90210_CountryShouldEqualUnitedStates(t *testing.T) {

	client := resty.New()

	resp, _ := client.R().Get("http://api.zippopotam.us/us/90210")

	myResponse := LocationResponse{}

	err := json.Unmarshal(resp.Body(), &myResponse)

	if err != nil {
		fmt.Println(err)
		return
	}

	assert.Equal(t, "United States", myResponse.Country)
}

As a final example, I’d like to show you how to create a setup method for creating an initial state for all the tests in a suite. Here’s how to do that using Testify and its suite module:

type ZippopotamUsTestSuite struct {
	suite.Suite
	ApiClient *resty.Client
}

func (suite *ZippopotamUsTestSuite) SetupTest() {
	suite.ApiClient = resty.New()
}

func (suite *ZippopotamUsTestSuite) Test_GetUs90210_StatusCodeShouldEqual200() {
	resp, _ := suite.ApiClient.R().Get("http://api.zippopotam.us/us/90210")

	assert.Equal(suite.T(), 200, resp.StatusCode())
}

First, we create a struct ZippopotamUsTestSuite that contains all the objects shared by all tests. In this case, all I need is a Client (a class in Resty) called ApiClient (in Go the variable name comes before the data type when you declare a new variable). We can then write a method called SetupTest() that does the setup for all of our tests, and we use (suite *ZippopotamUsTestSuite) to make our existing test methods part of the suite we defined.

To actually run our test suite, we need to create a ‘regular’ test method and pass the suite we have created to the suite.Run() method of Testify:

func TestZippopotamUsSuite(t *testing.T) {
	suite.Run(t, new(ZippopotamUsTestSuite))
}

If you omit this step, go test will not run the test suite!

All in all, given my experience is mostly with Java, C# and Python, I’ve found writing tests in Go to be a little more cumbersome than in those languages. However, with the right tool set, it is perfectly possible to write readable and well-structured tests in Go, as the examples in this blog post hopefully demonstrated.

I’m keen to further explore writing tests (and other software) with Go, so I’ve recently started learning the language through this Coursera course series. It’s mainly targeted towards those wanting to become a Go developer, and there isn’t much talk of testing, but I’m looking forward to it anyway. I’ll try to share some more tips and tricks on writing tests in Go in the near future.

If you want to read more about writing tests in Go, I recommend you reading this article by Alex Ellis or going through this tutorial.

All code that I’ve used in this blog post can be found here.

Writing tests for RESTful APIs in Python using requests – part 3: working with XML

Recently, I’ve delivered my first ever three day ‘Python for testers’ training course. One of the topics that was covered in this course is writing tests for RESTful APIs using the Python requests library and the pytest unit testing framework.

In this short series of blog posts, I want to explore the Python requests library and how it can be used for writing tests for RESTful APIs. This is the third blog post in the series, in which we will cover working with XML request and response bodies. Previous blog posts in this series talked about getting started with requests and pytest, and about creating data driven tests.

REST APIs and XML
While most REST APIs I encounter nowadays work with JSON as the preferred data format for request and response body bodies, from time to time you’ll encounter APIs that work with XML. And since XML is a little more cumbersome to work with XML in code compared to JSON (not just in Python, but in general), I thought it would be a good idea to show you some examples of how to create XML request bodies and how to parse and assert on XML response bodies when you’re working with the requests library.

For the examples in this blog post, I’ll be using an operation from the ParaBank REST API that can be used to submit bill payments. It’s available at

http://parabank.parasoft.com/parabank/services/bank/billpay

and takes, next to two query parameters specifying the source accountId and the bill amount, an XML request body containing specifics about the person to whom the payment is sent, i.e., the payee. Not surprisingly, this request body is sent to the API provider using an HTTP POST.

Creating XML request bodies using strings
I’d like to show you two distinct approaches to creating XML request bodies. The first one is the most straightforward one, but also the least flexible: creating a method that returns a string object containing the XML body:

def fixed_xml_body_as_string():
    return """
    <payee>
        <name>John Smith</name>
        <address>
            <street>My street</street>
            <city>My city</city>
            <state>My state</state>
            <zipCode>90210</zipCode>
        </address>
        <phoneNumber>0123456789</phoneNumber>
        <accountNumber>12345</accountNumber>
    </payee>
    """

Note the use of the triple double quotes to allow you to declare a multi-line string. Of course, instead of hard-coding our XML request body in our code, we could also read it from an XML (or text) file stored somewhere on our file system. The result is the same.

If we want to pass this XML request body to our API, we can do that like this:

def test_send_xml_body_from_string_check_status_code_and_content_type():
    response = requests.post(
        "http://parabank.parasoft.com/parabank/services/bank/billpay?accountId=12345&amount=500",
        headers={"Content-Type": "application/xml"},
        data=fixed_xml_body_as_string()
    )
    assert response.status_code == 200
    assert response.headers["Content-Type"] == "application/xml"

Note that we explicitly set the Content-Type header of the request to application/xml to make sure the provider understands that the request body should be interpreted as XML. Sending the XML request body is done by assigning the return value of our method returning the XML as a string to the data parameter of the requests post() method.

To check that our request has been received and processed successfully, we assert that the response status code equals 200 and that the response Content-Type header has a value of application/xml. We’ll take a closer look at the actual XML response body later on in this post.

Creating XML request bodies using ElementTree
The other approach to working with XML request bodies is to programmatically build them. Python contains a powerful library to do this, called ElementTree. We can import this into our module using

import xml.etree.ElementTree as et

Since an XML document is essentially a tree with a root node with child nodes attached to it, we start creating our XML request body by defining the payee root node:

payee = et.Element('payee')

We can then define an element name that is a child element of payee:

name = et.SubElement(payee, 'name')

We also need to assign an element value to the name element:

name.text = 'John Smith'

It’s not required for this example, but if you would have to add an attribute, say, type, with value fullName to the name element, you could do so like this:

name.set('type', 'fullName')

Creating the entire XML request body for our API call is a matter of repeating the above statements in the right order, with the right values:

def create_xml_body_using_elementtree():
    payee = et.Element('payee')
    name = et.SubElement(payee, 'name')
    name.text = 'John Smith'
    address = et.SubElement(payee, 'address')
    street = et.SubElement(address, 'street')
    street.text = 'My street'
    city = et.SubElement(address, 'city')
    city.text = 'My city'
    state = et.SubElement(address, 'state')
    state.text = 'My state'
    zip_code = et.SubElement(address, 'zipCode')
    zip_code.text = '90210'
    phone_number = et.SubElement(payee, 'phoneNumber')
    phone_number.text = '0123456789'
    account_number = et.SubElement(payee, 'accountNumber')
    account_number.text = '12345'
    return et.tostring(payee)

Note that we need to convert the element tree into a string before we can use it with the requests library. We can do this using the tostring() method.

While the approach using ElementTree might look a little more cumbersome than simply specifying our XML as a string, it gives us the option of creating more complex and flexible XML documents by creating loops to repeat XML blocks, working with data sources that are transformed into XML, and so on. I myself don’t really prefer one approach over the other, but I think it’s good to be aware of both and choose the one that best fits your requirements.

If we want to use the XML created using ElementTree above as a request body, we can do that in exactly the same way as when we used a string containing the XML:

def test_send_xml_body_from_elementtree_check_status_code_and_content_type():
    response = requests.post(
        "http://parabank.parasoft.com/parabank/services/bank/billpay?accountId=12345&amount=500",
        headers={"Content-Type": "application/xml"},
        data=create_xml_body_using_elementtree()
    )
    assert response.status_code == 200
    assert response.headers["Content-Type"] == "application/xml"

Parsing and working with XML response bodies
Now that we have covered creating XML request bodies, let’s see what we can do with XML responses. By far the most powerful way to create specific assertions is to convert the XML response body into an ElementTree and then asserting on its properties.

As an example, we’re going to perform an HTTP GET call to

http://parabank.parasoft.com/parabank/services/bank/accounts/12345

which returns details of the account with ID 12345. If we want to assert, for example, that the root node of the XML response is named account, and that it has neither any attributes nor a text value, we can do this as follows:

def test_check_root_of_xml_response():
    response = requests.get("http://parabank.parasoft.com/parabank/services/bank/accounts/12345")
    response_body_as_xml = et.fromstring(response.content)
    xml_tree = et.ElementTree(response_body_as_xml)
    root = xml_tree.getroot()
    assert root.tag == "account"
    assert len(root.attrib) == 0
    assert root.text is None

Note that we first have to convert the XML response body to an object of type Element using the fromstring() method, then create an ElementTree out of that using the ElementTree() constructor, which takes an Element as its argument.

If we’re interested in the text value of a specific subelement of the XML response, for example customerId which contains the ID of the customer to whom this account belongs, we can do that by finding it in the ElementTree using the find() method, then write an assertion on the text property of the found element:

def test_check_specific_element_of_xml_response():
    response = requests.get("http://parabank.parasoft.com/parabank/services/bank/accounts/12345")
    response_body_as_xml = et.fromstring(response.content)
    xml_tree = et.ElementTree(response_body_as_xml)
    first_name = xml_tree.find("customerId")
    assert first_name.text == "12212"

It’s good to know that the find() method returns the first occurrence of a specific element. If we want to return all elements that match a specific name, we need to use findall() instead:

def test_use_xpath_for_more_sophisticated_checks():
    response = requests.get("http://parabank.parasoft.com/parabank/services/bank/customers/12212/accounts")
    response_body_as_xml = et.fromstring(response.content)
    xml_tree = et.ElementTree(response_body_as_xml)
    savings_accounts = xml_tree.findall(".//account/type[.='SAVINGS']")
    assert len(savings_accounts) > 1

As you can see, next to passing element names directly, we can also use XPath expressions to perform more sophisticated selections. The expression

 .//account/type[.='SAVINGS']

in the example above selects all occurrences of the type element (a child element of account) that have SAVINGS as their element value.

Using the examples for yourself
The code examples I have used in this blog post can be found on my GitHub page. If you download the project and (given you have installed Python properly) run

 pip install -r requirements.txt

from the root of the python-requests project to install the required libraries, you should be able to run the tests for yourself. See you next time!

2019 – a year in review

2019 is almost at an end and wow, what a year it has been. A lot of ups, some downs, but a major net positive overall. As I did last year, with this post I’d like to reflect a little on all that I’ve been working on this year, and share with you some of my plans for 2020, too.

Training
As I said last year, my main goal for 2019 was to extend my training efforts. I am pretty happy with how this turned out, overall. While the year started a little slow, it took off around May, with a couple of downright crazy weeks around October and November. In total, I delivered 30 full days of in company training and another 19 half day and evening training sessions, with 15 different clients. Collaborating with training companies helped a lot in getting to this result, and I’m looking forward to continuing working with all of them next year.

Most of the training I delivered featured writing automation in Java, with C# coming in second, Python at #3 and here and there some JavaScript, too.

Adding to that, I have done two full day conference workshops (one at the Agile & Automation Days in Poland, the other at TestBash Netherlands) and three half-day workshops (at the TestNet and Test Automation Days conferences and one at a meetup).

The highlight of this year with regards to training was probably my trip to the UK in November to deliver two full days of in-company API testing and automation training. One thing I need to work on next year is finding a little more balance in the busy and the slow weeks and months by building a steadier pipeline of training work.

Consulting
I worked with two consulting clients this year. The one I spent the most time with was an on site gig here in the Netherlands, where I was (and still am) tasked with coaching a number of testers (and entire development teams) with the implementation of test automation. It’s a really interesting and fulfilling gig that will continue at least in the first couple of months of next year, and I’m really happy with the results we’ve booked and the progress we have made.

Since October, I’m also doing some remote consulting with (and writing for) a consultancy firm in the United States, and so far this has been a really interesting and rewarding gig, too. I’d love to build on this relationship in 2020 and maybe find some other remote consulting clients, too. The idea of literally being able to work from anywhere, for organizations anywhere on this planet, without having to commute, is something that really appeals to me. So, if you are or know of an organization that could use some advice or consulting in the area of test automation, contact me, I’d love to talk and see if I can help you in 2020.

Writing
2019 has been a pretty active year in writing for me, too. I have written and published 10 articles on various industry blogs and websites, and another 8 blog posts (including this one) on this site. I will continue writing next year, as I think it’s still a great way to process and structure my thoughts, as well as a good excuse to learn new things myself.

Public speaking
This year, I’ve done 9 talks, mostly at meetups and conferences, but also one in-house with a client. Six of these were regular talks, one was a deep dive with some live coding, but most notably I have done two international keynotes, one at UKStar (London, in March) and the other at the Agile & Automation Days (Poland, in October). I really enjoyed both these talks and have received some good and constructive feedback on them.

Even though I’ll mostly be focusing on doing workshops at conferences and meetups (since that’s what I like to do best), I hope to be able to do a couple of talks next year, too. I’ve got one conference planned so far (in June) and hope to add a couple more to the agenda as 2020 progresses.

Other activities
Apart from all that I mentioned above, I’ve done one webinar this year (with Parasoft), appeared as a guest on a podcast (with the fine people that host de Voorproeverij) and had my first online course published with Test Automation University. I’m looking forward to seeing what opportunities 2020 will bring me.

The freelance life
No surprises here: I thoroughly enjoyed working as a freelancer this year, and I’m even more convinced that this is the most ideal way of working for me, at least for the next couple of years. The total freedom of going where I want to go and working on what I want to work on has been treating me very well again this year. It has also given me the opportunity to be there for my family when that was needed, without having to go through hoops or having to account for fewer hours or days worked. I’m very much looking forward to another year of freelancing in 2020.

For now, though, it’s time to wind down for a couple of weeks and recharge. Here’s to 2020 becoming an awesome year for all of us.