Testing your APIs with Cucumber

Reading time ~9 minutes

A well rounded test suite gives you confidence that your code is stable and can handle a variety of use cases. It can also help discover issues before your customers do. End to end testing comes under fire a bit for being more trouble than it’s worth. Many of the issues are a side effect of maintenance around front-end testing. Changing a label or a class on an item on the view can cause a failure in your test suite. API end to end testing can prove pretty valuable however and since it’s decoupled from the view the focus is on testing accurate response payloads, HTTP response codes, Authenticated responses, and other API contracts. It’s fairly simple to set up, configurable, and you can even add the test suite as a job to your Jenkins build. I bet you can could even get testing set up quicker than you can finish reading this blog post. So let’s get started.

We’re going to use a couple of different tools to get this test stack up and running. What we’ll cover:

  • Ruby installation
  • What/Why Cucumber?
  • Setting up an API
  • Setting up Cucumber

Step 1: Ruby installation

To get ruby up and running I would recommend using RVM (Ruby Version Manager). You can get RVM from rvm.io. This will allow you to use different versions of Ruby with different supported gem sets. Once you’ve installed and set up ruby make sure it’s working by running the following command in your terminal ruby -v. We’re targeting ruby version 2.0 for now so if you don’t have that you can run rvm install 2.0 to get it installed. Next run rvm use 2.0 to activate it in your shell. Make sure also that you have bundler installed. We’ll need that later. Do a quick check by entering bundle -v in your shell. If that doesn’t work simply run gem install bundler. Alright, we got our precious gemstone setup, let’s gnash on some veggies.

Step 2: What/Why Cucumber?

You may be wondering why Cucumber? What is Cucumber? Cucumber is a way for running BDD (Behavior Driven Development) tests using a pseudo language called Gherkin (Gherkin is also a small cucumber used in pickling). Cucumber maps the Gherkin to underlying ruby code used to run your tests. The beauty of using this format is that even non-programmers can write the tests to run. People familiar with Scrum & Agile will note that the BDD tests are very similar if not identical to Acceptance Criteria you may see attached to your stories. This structure follows the Given…When…Then… format.

Here’s a basic example: Given I navigate to a homepage When the page is loaded Then I see the company logo

This is a pretty common example of how cucumber tests have existed in the past where they’ve been tasked to test the front end of web and mobile apps. This can get a bit cumbersome as many testers are forced to keep track of view elements and their labels, classes, and other properties. Also, the test suite can fail if a slow loading page or app doesn’t load in time. This means the test suite can take some time to load as it waits for timeouts and sleeps to tick away. API endpoint testing will be fairly slow as well since they don’t rely on views to load the API tests will be comparably faster. Fair warning though, you probably don’t want to run your Cucumber tests against your production environment because, well it’s production, and hopefully any issues should have already been ferreted out on your local or dev environments.

Step 3: Setting up an API or a wild API emerges

So we need an API. If you have one ready to go, awesome, use that. If not then we’ll get one rigged up really quick. For the purpose of this demo I’m going to recommend that we use the Loopback Node.js API framework from the folks at Strongloop. It’s a bit like Express on steroids (in fact the Strongloop team has taken over the Express project). If you have another API you can use feel free to skip ahead. Before you go though, with this kind of testing because the Cucumber tests will run against a hostname and not anything in the relative path. You can put the Cucumber test suite anywhere in the file system you’d like. I’m including it in the directory of my API code because I like to keep tests in sight so that they don’t get forgotten.

Installing Strongloop

Hopefully you have Node installed. If not, I’m not going to cover it during this exercise. However, if you are going to install it much like RVM there is a NVM package that can help you manage multiple versions of Node and running node_modules in isolation. You can learn more about NVM at https://github.com/creationix/nvm.

To install the Strongloop stack simply run npm install -g strongloop. Once Strongloop is installed the next step is to create an app and a model.

Yeeeoooooo-man

  1. Create an app: slc loopback cuke-test
  2. Choose a name. In our case it will default to: cuke-test so press Enter a couple times.
  3. Now cd cuke-test.
  4. Create a model using the slc loopback:model command.
  5. Choose db(memory) for the storage mechanism and PersistedModel for the base class.
  6. Give the model a name property of type string and make it required.
  7. A color property of type string and make it required as well.
  8. A yummy property of type boolean and this doesn’t have to be required.

At this point you should be able to run slc run from the root of the app directory and navigate to http://0.0.0.0:3000/explorer to see the REST API Explorer.

We’re going to do one more thing before before we move on. Loopback has a feature in their framework called Remote Methods. You can use remote methods to customize the behavior when certain endpoints are requested. We’re going to add a remote method on the Veggie model that gets triggered whenever anyone navigates to /veggies/teapot. We’re going to customize the response of that endpoint to change the status code of the http response to be 418 and return a message that says, “I am a teapot”. Feel free to copy and paste the following code into your /common/models/veggie.js file.

    module.exports = function(Veggie) {

      Veggie.teapot = function(cb) {
        var data = {
          'message': 'I am a teapot',
        }
        cb(null, data);
      }

      Veggie.afterRemote('teapot', function(ctx, instance, next) {
        ctx.res.statusCode = 418;
        next();
      });

      Veggie.remoteMethod(
        'teapot',
        {
          http: { verb: 'get' },
          returns: { arg: 'data', type: 'object', root: true },
        }
      );
    };

Step 4: Setting up Cucumber

Now that we have our API ready to go the next step is to start folding in the Cucumber test suite. To install Cucumber simply run gem install cucumber. Verify that Cucumber is installed by running cucumber --version.

We’re going to add a cucumber directory to the root of the API folder structure

[cuke-test]
    \_[client]
    \_[common]
    \_*[cucumber]*
    \_[node_modules]
    \_..etc..

Inside the cucumber directory create a file named Gemfile and create a directory called features. In the Gemfile we’ll be adding

source 'https://rubygems.org'

gem 'cucumber'
gem 'cucumber-api'

In the features directory we’ll need to add two directories (support and veggies) so it looks like this:

[cucumber]
    \_support
    \_veggies

In the support directory create a file called env.rb. Add the following line of code: require ‘cucumber-api’

In the veggies directory add a file called veggies.feature. We’re going to start just by POSTing a veggie payload to the API.

Feature: Veggies

  Scenario: Add a veggie
    When I send and accept JSON
    And I set form request body to:
      | name    | tomato     |
      | color   | red        |
      | yummy   | true       |
    And I send a POST request to "http://0.0.0.0:3000/api/veggies"
    Then the response status should be "200"

As you see above we’re adding a Feature of Veggies. Then defining a Scenario. Since Loopback sends and receives JSON by default the When I send and accept JSON is setting up the Content-Type and Accepts headers. Next we’re creating a form body and sending a POST request to our API endpoint. Later (probably in a Part 2) I’ll show you how to make the hostname configurable and how to write your own step definitions. Below you’ll see I’ve written a few other tests as well. One that tests a bad payload, one that tests a GET operation, and one that tests the custom Remote Method that we set up earlier.

Scenario: Bad Params Veggie
    When I send and accept JSON
    And I set form request body to:
      | name    | rotten     |
      | yummy   | false      |
    And I send a POST request to "http://0.0.0.0:3000/api/veggies"
    Then the response status should be "422"

Scenario: Get veggies
  When I send and accept JSON
  And I send a GET request to "http://0.0.0.0:3000/api/veggies"
  Then the response status should be "200"

Scenario: Hit Teapot Endpoint
  When I send and accept JSON
  And I send a GET request to "http://0.0.0.0:3000/api/veggies/teapot"
  Then the response status should be "418"
  And the JSON response should have key "message"

That’s it. Apart from the custom code we wrote in the Remote Method stuff for Loopback we’ve written pretty zero code for our test suite relying almost exclusively on Gherkin to define our test cases. As we extend the tests and make it more configurable this will change but I wanted to show you how simple it is to get up and running.

Running the Tests

In order to run the tests go to the cucumber directory of your project and run the following command:

cucumber cucumber_api_verbose=true

The cucumber_api_verbose option is going to give us a little more output so that we can debug a little easier if something fails. If all goes well you should see the following:

Feature: Veggies
Scenario: Add a veggie
When I send and accept JSON
And I set form request body to:
| name | tomato |
| color | red |
| yummy | true |
And I send a POST request to "http://0.0.0.0:3000/api/veggies"
Then the response status should be "200"
Scenario: Bad Params Veggie
When I send and accept JSON
And I set form request body to:
| name | rotten |
| yummy | false |
RestClient.post "http://0.0.0.0:3000/api/veggies", "name=rotten&yummy=false", "Accept"=>"application/json", "Accept-Encoding"=>"gzip, deflate", "Content-Length"=>"23", "Content-Type"=>"application/x-www-form-urlencoded", "Content-type"=>"application/json"
# => 422 UnprocessableEntity | application/json 408 bytes
And I send a POST request to "http://0.0.0.0:3000/api/veggies"
Then the response status should be "422"
Scenario: Get veggies
When I send and accept JSON
RestClient.get "http://0.0.0.0:3000/api/veggies", "Accept"=>"application/json", "Accept-Encoding"=>"gzip, deflate", "Content-type"=>"application/json"
# => 200 OK | application/json 469 bytes
And I send a GET request to "http://0.0.0.0:3000/api/veggies"
Then the response status should be "200"
Scenario: Hit Teapot Endpoint
When I send and accept JSON
RestClient.get "http://0.0.0.0:3000/api/veggies/teapot", "Accept"=>"application/json", "Accept-Encoding"=>"gzip, deflate", "Content-type"=>"application/json"
# => 418 ClientError | application/json 27 bytes
And I send a GET request to "http://0.0.0.0:3000/api/veggies/teapot"
Then the response status should be "418"
And the JSON response should have key "message"
4 scenarios (4 passed)
15 steps (15 passed)
0m0.097s

Success!

All green and good to go! With just a few commands and a couple lines of code we now have a fully functional BDD test suite testing our API endpoints. Whether the goal is to make sure our API contracts don’t break or our API endpoints are functional, we have a lightweight test suite that we can run locally during development or as part of a build process. In a future post I’ll show you set up your Jenkins build to run these test, set up environmental variables, and cache token responses for future use during subsequent Cucumber API tests. I hope you found some value in this post. If you have any questions, comments, or issues feel free to contact me via the comment section below.

Happy coding!

Writing Node.js Apps from Scratch

Satirical posts regarding the bloat of modern day apps are a hot topic of both memes and [dev discussion boards](https://hackernoon.com...… Continue reading

Servicing Your Actual Customer

Published on May 26, 2016

Basketball and Startups

Published on July 29, 2015