BDD Tips - Mocking an API
Why might you mock an API?
API's, whether they be REST, SOAP or the dreaded 'banter' variety, play central roles in both client-side and server-side application behaviour. Most web applications facilitate user interaction through API-driven javascript clients. Over on the server side, whether PHP, Nodejs or other flavours, 3rd-party APIs feature in behaviour such as importing exchange-rates or more complicated user authentication.
I'm trying to follow an outside-in development approach as much as I can. When this goes to plan I'll start with feature definitions using storyBDD tools such as Behat and Cucumber. Once I've a failing feature, I can move to the next phase - the actual implementation employing specBDD frameworks such as PHPSpec and Mocha.
One of the major barriers to following this approach has occurred with application behaviour that involves API integration, particularly when in the storyBDD phase. SpecBDD has been fine - you're never making actual requests, you're just mocking data exchange between your objects.
However, in storyBDD you test your entire system. It must perform actual HTTP requests to an endpoint allowing you to confirm the system behaves correctly end to end. This is often tricky, sometimes you may not even have an API to test against. What's more, in order to ensure you've plenty of scenarios to cover your system, you'll probably need receive different responses for the same request - i.e. differing datasets or HTTP status codes. In the past I've addressed this by:
- Getting lucky and having a test API to call
- Writing one-off, dummy servers in nodejs
- Manually testing scenarios when a lack of API responses renders automation difficult
A couple of weeks ago I stumbled across RoboHydra, an HTTP client test tool developed at Opera. I'm finding it incredibly useful for BDD when stories require API interaction. It allows you to describe how an HTTP endpoint should behave and is written in a way which allows you to control this behaviour from within your storyBDD step definitions.
RoboHydra?
RoboHydra is a nodejs web server which can quickly be configured to provide specific HTTP responses for given requests. A simple example for the endpoint GET /users
might be:
{
name : "users",
path : "/users",
content : [ { "id" : 1, "name" : "rob" } ]
}
If you were to start RoboHydra and submit an HTTP request to http://localhost:3000/users
(3000 is the default port), you'll get the pre-defined response. These rules can be pre-configured in static files or dynamically at runtime.
RoboHydra's documentation is pretty good and covers both basic and advanced configurations. However, the features I deem to be the secret sauce for BDD are as follows:
1.Heads
A head
specifies what RoboHydra should return for a given url path (or matched regex). You can control all sorts of things, the body, response headers even response latency. A head also accepts a callback function, allowing you to define more precisely how the endpoint should behave.
2.Tests
A test
is a specific scenario linked to a head which, when activated, overrides the response that would normally be delivered. For example, you might want to throw a 500 error from that /users
endpoint to see how your application reacts:
{
name: 'users_failure',
path : "/users",
statusCode : 500,
content : "Nasty application failure",
}
3.Plugins
RoboHydra is configured and extended using plugins. Plugins are nodejs modules which RoboHydra includes on start up. As an example, you could easily configure a head to read in a static file and serve it back as a response:
{
name: 'rate-usd',
path: '/currency-rates/usd',
contentType: 'application/xml'
content: FS.readFileSync('us-rates.xml'), //read in from file system
}
4.Admin plugin
The admin plugin provides a web UI, displaying the heads/tests which are configured and allows you to activate/deactivate them. What's more, the admin plugin has a public HTTP API that can be used to interact with RoboHydra at runtime. You can add heads, activate/deactive tests and perform other operations from any external environment....including your step definitions!
RoboHydra and storyBDD
I've recently been using Cucumber as my storyBDD framework for a small command line tool written in nodejs. I'm going to take some examples from this to demonstrate how RoboHydra can be integrated into a storyBDD development phase.
Scenario: A user is imported from the accounting system
Given no users are stored
When I run the command 'import:users'
Then the folllowing users will be stored:
| id | name | VAT Number |
| 1 | rob | 09890999 |
As the workflow demonstrates, I kick off the RoboHydra server (using nodejs's spawn
command) at the beginning of the feature's execution. No further interaction is required as the mock response is activated on startup. The application calls the API and the predefined users collection is returned. It's then a matter of checking the database to ensure the system is behaving correctly.
Scenario: The accounting system fails
Given the following users are stored:
| id | name | VAT Number |
| 1 | rob | 09890999 |
And the accounting system is failing
When I run the command 'import:users'
Then the folllowing users will be stored:
| id | name | VAT Number |
| 1 | rob | 09890999 |
And a log entry will exist
This scenario is a little more difficult to test, this time I need the API to throw a 500 error when the endpoint is hit. As I've explained, tests exist in RoboHydra to temporarily alter the behaviour of heads and can be stopped and started over HTTP. During the step definition for the accounting system is failing
, I make a call to RoboHydra to activate the failure
test. When the next step definition is run, the API throws a 500 error precisely when I need it to.
You can use a similar techique to create new heads from within your step definitions. This provides ultimate flexibility and allows you to mock your API directly from a Gherkin data table, for example:
Scenario: A new user has been added to the accounting system
Given the following users are stored:
| id | name | VAT Number |
| 1 | rob | 09890999 |
And the following users exist in the accounting system:
| id | name | VAT Number |
| 2 | will | 09890333 |
When I run the command 'import:users'
Then the folllowing users will be stored:
| id | name | VAT Number |
| 1 | rob | 09890999 |
| 2 | will | 09890333 |
Although this approach renders your step definitions more complex, (you'll need to parse your data table into the API's schema before you send to RoboHydra), it allows your feature files to be much more descriptive. What's more, by using a templating engine to parse your data tables you can clearly document how your system is expecting the API to behave.
These techniques have provided me enough flexibilty to mock the most tricky of endpoints in no time at all. I was even able to mock a SOAP api in just a couple of hours - look out for more info on that over on UVd's blog. Despite using a simple command line example, the principles still apply to web apps, mobile apps and any instances where a system feature involves an HTTP API. Hopefully I've also demonstrated that RoboHydra is well worth drafting into your BDD toolkit.