Slide 1

Slide 1 text

• Friday, October 7, 11

Slide 2

Slide 2 text

Testing Web APIs with Ruby and Cucumber Anthony Eden Friday, October 7, 11

Slide 3

Slide 3 text

As a developer In order to build rock-solid web APIs I will test like a boss Friday, October 7, 11

Slide 4

Slide 4 text

Like a Boss Friday, October 7, 11

Slide 5

Slide 5 text

Why Cucumber? Friday, October 7, 11

Slide 6

Slide 6 text

Show me the Money Friday, October 7, 11

Slide 7

Slide 7 text

Feature: create a card As an API client In order to add a person to the address book I can create a card Friday, October 7, 11

Slide 8

Slide 8 text

Scenario: Given I send and accept JSON When I send a POST request to "/cards" with the following: ... Then the response status should be "201" And the response body should be a JSON representation of the Card Friday, October 7, 11

Slide 9

Slide 9 text

Deeper Down the Rabbit Hole Friday, October 7, 11

Slide 10

Slide 10 text

Scenario: Given I send and accept JSON When I send a POST request to "/cards" with the following: """ { "first_name":"Anthony", "last_name":"Eden", "display_name":"Anthony Eden", "emails":[ { "address_type":"personal", "address":"anthonyeden@gmail.com" }, { "address_type":"work", "address":"anthony@dnsimple.com" } ] } """ Then the response status should be "201" And the response body should be a JSON representation of the Card Friday, October 7, 11

Slide 11

Slide 11 text

Given I send and accept JSON Friday, October 7, 11

Slide 12

Slide 12 text

Given /^I send and accept JSON$/ do header 'Accept', 'application/json' header 'Content-Type', 'application/json' end Friday, October 7, 11

Slide 13

Slide 13 text

When I send a POST request to "/cards" with the following: Friday, October 7, 11

Slide 14

Slide 14 text

When /^I send a POST request to "([^\"]*)" with the following:$/ do |path, body| post path, body end Friday, October 7, 11

Slide 15

Slide 15 text

Then the response status should be "201" Friday, October 7, 11

Slide 16

Slide 16 text

Then /^the response status should be "([^"]*)"$/ do |status| last_response.status.should eq(status.to_i) end Friday, October 7, 11

Slide 17

Slide 17 text

Then /^the response status should be "([^"]*)"$/ do |status| begin last_response.status.should eq(status.to_i) rescue RSpec::Expectations::ExpectationNotMetError => e puts "Response body:" puts last_response.body raise e end end Friday, October 7, 11

Slide 18

Slide 18 text

And the response body should be a JSON representation of the Card Friday, October 7, 11

Slide 19

Slide 19 text

Then /^the response body should be a JSON representation of the (\w+)$/ do |model| last_response.body.should eq(model.constantize.last.to_json) end Friday, October 7, 11

Slide 20

Slide 20 text

Storing a bit of state (like a client app) Friday, October 7, 11

Slide 21

Slide 21 text

Given I have created a card And I store the card id to use it in a future API call When I send a GET request to "/cards/{{id}}" Friday, October 7, 11

Slide 22

Slide 22 text

Given /^I store the card id to use it in a future API call$/ do @id = Card.last.id end Friday, October 7, 11

Slide 23

Slide 23 text

When I send a GET request to "/cards/{{id}}" Friday, October 7, 11

Slide 24

Slide 24 text

When /^I send a GET request to "([^"]*)"$/ do |path| path = Mustache.render(path, {:id => @id}) get path end Friday, October 7, 11

Slide 25

Slide 25 text

When /^I send a GET request to "([^"]*)" with the last (\w+) id$/ do |path, model| id = model.constantize.last.id path = Mustache.render(path, {:id => id}) get path end Friday, October 7, 11

Slide 26

Slide 26 text

Verification of Response Data Friday, October 7, 11

Slide 27

Slide 27 text

github.com/collectiveidea/json_spec github.com/fd/json_select Friday, October 7, 11

Slide 28

Slide 28 text

Then /^the first name in the JSON representation of the card should be "([^"]*)"$/ do |first_name| json = JSON.parse(last_response.body) match = JSONSelect('.first_name').match(json) match.should eq(first_name) end Friday, October 7, 11

Slide 29

Slide 29 text

What about our old friend, XML? Friday, October 7, 11

Slide 30

Slide 30 text

Scenario: create a card with XML Given I send and accept XML When I send a POST request to "/cards" with the following: """ Anthony Eden Anthony Eden """ Then the response status should be "201" And the response body should be an XML representation of the Card Friday, October 7, 11

Slide 31

Slide 31 text

Given /^I send and accept XML$/ do header 'Accept', 'text/xml' header 'Content-Type', 'text/xml' end Friday, October 7, 11

Slide 32

Slide 32 text

Then /^the response body should be an XML representation of the (\w+)$/ do |model| last_response.body.should eq(model.constantize.last.to_xml) end Friday, October 7, 11

Slide 33

Slide 33 text

The Setup Friday, October 7, 11

Slide 34

Slide 34 text

Rails Friday, October 7, 11

Slide 35

Slide 35 text

gem 'rspec' gem 'cucumber-rails' gem 'database_cleaner' gem 'mustache' gem 'json_select' Friday, October 7, 11

Slide 36

Slide 36 text

Sinatra Friday, October 7, 11

Slide 37

Slide 37 text

env.rb Friday, October 7, 11

Slide 38

Slide 38 text

class AddressBookWorld include RSpec::Expectations include RSpec::Matchers include Rack::Test::Methods def app Sinatra::Application end end Friday, October 7, 11

Slide 39

Slide 39 text

class String def constantize split('::').inject(Object) { |memo,name| memo = memo.const_get(name); memo } end end Friday, October 7, 11

Slide 40

Slide 40 text

The Versioning Debate Friday, October 7, 11

Slide 41

Slide 41 text

/v1/cards Friday, October 7, 11

Slide 42

Slide 42 text

Accept: application/vnd.myapp+json; version=1 Friday, October 7, 11

Slide 43

Slide 43 text

No Versioning Friday, October 7, 11

Slide 44

Slide 44 text

github.com/evome/biceps Friday, October 7, 11

Slide 45

Slide 45 text

HATEOAS Friday, October 7, 11

Slide 46

Slide 46 text

"links": [ { "rel": "addresses", "uri": "/cards/1/addresses" }, { "rel": "self", "uri": "/cards/1" } ] Friday, October 7, 11

Slide 47

Slide 47 text

Friday, October 7, 11

Slide 48

Slide 48 text

RSpec for Integration Friday, October 7, 11

Slide 49

Slide 49 text

require 'spec_helper' describe "create card" do context "through the API" do let(:headers) do { 'HTTP_ACCEPT' => 'application/json', 'HTTP_CONTENT_TYPE' => 'application/json' } end let(:body) do { "first_name" => "Anthony", "last_name" => "Eden", "display_name" => "Anthony Eden", } end it "creates a card" do post '/cards', body.to_json, headers response.status.should eq(201) response.body.should eq(Card.last.to_json) end end end Friday, October 7, 11

Slide 50

Slide 50 text

it "creates a card" do post '/cards', body.to_json, headers response.status.should eq(201) response.body.should eq(Card.last.to_json) end Friday, October 7, 11

Slide 51

Slide 51 text

The Future of Web Applications Friday, October 7, 11

Slide 52

Slide 52 text

Things to Think About Friday, October 7, 11

Slide 53

Slide 53 text

How would building services as only APIs affect your choice of tools and techniques? Friday, October 7, 11

Slide 54

Slide 54 text

Are there programming languages that would be better suited to API-only services? Friday, October 7, 11

Slide 55

Slide 55 text

What other protocols are useful for building API-only services? Friday, October 7, 11

Slide 56

Slide 56 text

Fuzz test your beliefs Friday, October 7, 11

Slide 57

Slide 57 text

github.com/aeden/demo_address_book Friday, October 7, 11

Slide 58

Slide 58 text

http://www.flickr.com/photos/jordanfagendotcom/5474546369/ http://www.flickr.com/photos/marcelgermain/2162363274/ http://www.flickr.com/photos/lokacid/314928604/ http://www.flickr.com/photos/14545665@N04/3473711345/ http://www.flickr.com/photos/frinky/2593516800/ http://www.flickr.com/photos/stathis1980/4285295041/ Friday, October 7, 11

Slide 59

Slide 59 text

Friday, October 7, 11

Slide 60

Slide 60 text

Friday, October 7, 11

Slide 61

Slide 61 text

Blog: http://anthonyeden.com Twitter: @aeden Friday, October 7, 11