Mock APIs with API Blueprint, Dredd, api-mocks, and rails22 Nov 2015
Here's a story we're all too familiar with: you're developing an API and webapp at the same time. API first is all the rage these days. At some point frontend developers will need to connect to a backend API. They can mock out API behavior for a little while, but may wind up in trouble if the final API is very different than what was expected. You don't want to rush API development, but you need to share something with the people developing the frontend...sooner rather than later.
Now this may sound obvious, but the thing about coding is this: you don't just start writing code. Instead, you spend a good chunk of time figuring out what to build. The better idea you have of what needs to be built, the faster you'll be able to build it once you get started. You're also less likely to build features that aren't needed or introduce unnecessary code (less is more!). Just like the frontend, the design phase is equally important for the API.
Rather than starting to code the API, wouldn't it be nice if you could:
- Design the API using a modeling language that is programming language agnostic.
- Provide frontend developers with a mocked API that matches the specification above, with no additional development effort.
- Implement the actual API such that it behaves according to the same specification as the mocked API.
Thankfully there are several such API modeling languages out there that make it relatively straightforward to accomplish the goals above. The main competitors are:
Looking for some comparisons? Check out the following articles:
The general consensus seems to be that there's no clear winner in the API modeling language game. PLUS--there's tools to convert between modeling languages, so migration is always a potential option should one of the modeling langauges become deprecated down the road.
For my project, I chose API Blueprint for the following reasons:
- Easy to understand (markdown is pretty easy to read)
- Easy to write (markdown)
- Tooling seems decent (dredd, api-mock, and many more)
Ok, so how do I get started with all of this?
Start by installing dependencies
Please note that I'm working on a Mac and using Homebrew so my notes are for that kind of setup.
node (using v0.10.40)
api-mock are node tools so you'll need node.
brew install nodejs
dredd (v1.0.1) / api-mock (v0.2.2)
npm to install
npm install -g dredd api-mock
ruby2.2 / rails4.2
gem install --no-ri --no-rdoc rails bundler
Great, now we can get started.
Time to create the dummy API
We want our API to have a single endpoint located at
/dummy. It will respond to a
GET request with the following plaintext:
hello world. If we're using curl, it would behave like this:
The associated specification (a.k.a "spec") file describing this API would look like this:
# My API ## GET /dummy + Response 200 (text/plain) hello world
Mock API Server
To run this mock API, run this:
You now have a dummy mocked API (on
All is fine and dandy, but what do you do when you want to start implementing this api in some language like ruby, python, go, etc? That's where dredd comes in. The tagline for
> Dredd is a language agnostic command-line tool for testing API documentation written in the API Blueprint format against its backend implementation.
This means that:
- Your API documentation can always be up to date (use a CI system like Travis or Jenkins to accomplish this).
- You can verify that the API you build conforms to the same standards as the mocked api.
dredd has an interactive configuration mode so you can get started quickly. For our example, run
dred init to get started:
This results in a
dredd.yml file that looks like this:
NOTE: Ignore the
hookfiles part right now. We'll get to that soon.
Implement the dummy API
Before creating the implementation, lets see what happens if we just try running this by itself:
Dredd attempted to start up our api...and it didn't exist. Can't test something that doesn't exist! I'll quickly setup a quick rails app.
We now see 1 failing test (
/dummy), with the following errors:
fail: headers: Header 'content-type' has value 'text/html; charset=utf-8' instead of 'text/plain; charset=utf-8'
body: Real and expected data does not match.
statusCode: Status code is not '200'
I haven't set any endpoints up yet, so these errors are letting me know what's missing.
Now to test again...
$ dredd Configuration dredd.yml found, ignoring other arguments. Starting server with command: bundle exec rails s Waiting 3 seconds for server command to start... [2015-11-22 23:08:44] INFO WEBrick 1.3.1 [2015-11-22 23:08:44] INFO ruby 2.2.2 (2015-04-13) [x86_64-darwin14] [2015-11-22 23:08:44] INFO WEBrick::HTTPServer#start: pid=52409 port=3000 info: Beginning Dredd testing... fail: GET /dummy duration: 155ms info: Displaying failed tests... fail: GET /dummy duration: 155ms fail: body: Real and expected data does not match. request: body: headers: User-Agent: Dredd/1.0.1 (Darwin 15.0.0; x64) uri: /dummy method: GET expected: headers: Content-Type: text/plain; charset=utf-8 body: hello world statusCode: 200 actual: statusCode: 200 headers: x-frame-options: SAMEORIGIN x-xss-protection: 1; mode=block x-content-type-options: nosniff content-type: text/plain; charset=utf-8 etag: W/"bd13b94ec091c54f6f01d47ce47a54a5" cache-control: max-age=0, private, must-revalidate x-request-id: bc936f24-2dcb-4e96-8c03-3673d8edaf01 x-runtime: 0.114245 server: WEBrick/1.3.1 (Ruby/2.2.2/2015-04-13) date: Mon, 23 Nov 2015 07:08:46 GMT content-length: 16 connection: Keep-Alive body: hello world complete: 0 passing, 1 failing, 0 errors, 0 skipped, 1 total complete: Tests took 158ms
What's going on here? Well, it turns out there there's some extra whitespace in a
text/plain response. This is documented here. This is where the hook system comes into play.
Dredd hooks are documented here. Common use cases for hooks include (taken from the dredd documentation):
- loading db fixtures
- cleanup after test step or steps
- handling authentication and sessions
- passing data between transactions (saving state from responses to stash)
- modifying request generated from blueprint
- changing generated expectations
- setting custom expectations
- debugging via logging stuff
In our case we can use the hooks to clean up the newline character in the expected result. For ruby/rails, this means that we'll need to install the
gem install --no-ri --no-rdoc dredd_hooks
Then we'll create a
hooks.rb file that looks like the following:
Now to run dredd again (important:
hookfiles must be set in
Success! You have now accomplished the following:
- Created an API spec using API Blueprint.
- Created a Mock API for the API Spec (this can be used by anyone working on the frontend--and yes, they can work offline with the mock api).
- Used dredd to test the implemented API against the specification. We used ruby, but language really doesn't matter here!