Testing your APIs with Cucumber
Written By: Tommy Ryan | Published: 2015-08-02
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.
- Create an app:
slc loopback cuke-test
- Choose a name. In our case it will default to:
cuke-test
so press Enter a couple times. - Now
cd cuke-test
. - Create a model using the
slc loopback:model
command. - Choose
db(memory)
for the storage mechanism andPersistedModel
for the base class. - Give the model a
name
property of typestring
and make it required. - A
color
property of typestring
and make it required as well. - A
yummy
property of typeboolean
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!