Testing Web APIs with Ruby and Cucumber

Testing Web APIs with Ruby and Cucumber

Web apps are hot, web apps with APIs are even hotter. These days popular sites are opening up more and more of their functionality via web APIs and you should too. This talk will cover how to develop web APIs with Ruby, either using Rails or Sinatra. It will also cover how to test those APIs with Cucumber.

31254903db793bf6f84bbd607fe092fd?s=128

Anthony Eden

October 07, 2011
Tweet

Transcript

  1. • Friday, October 7, 11

  2. Testing Web APIs with Ruby and Cucumber Anthony Eden Friday,

    October 7, 11
  3. As a developer In order to build rock-solid web APIs

    I will test like a boss Friday, October 7, 11
  4. Like a Boss Friday, October 7, 11

  5. Why Cucumber? Friday, October 7, 11

  6. Show me the Money Friday, October 7, 11

  7. 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
  8. 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
  9. Deeper Down the Rabbit Hole Friday, October 7, 11

  10. 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
  11. Given I send and accept JSON Friday, October 7, 11

  12. Given /^I send and accept JSON$/ do header 'Accept', 'application/json'

    header 'Content-Type', 'application/json' end Friday, October 7, 11
  13. When I send a POST request to "/cards" with the

    following: Friday, October 7, 11
  14. When /^I send a POST request to "([^\"]*)" with the

    following:$/ do |path, body| post path, body end Friday, October 7, 11
  15. Then the response status should be "201" Friday, October 7,

    11
  16. Then /^the response status should be "([^"]*)"$/ do |status| last_response.status.should

    eq(status.to_i) end Friday, October 7, 11
  17. 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
  18. And the response body should be a JSON representation of

    the Card Friday, October 7, 11
  19. 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
  20. Storing a bit of state (like a client app) Friday,

    October 7, 11
  21. 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
  22. Given /^I store the card id to use it in

    a future API call$/ do @id = Card.last.id end Friday, October 7, 11
  23. When I send a GET request to "/cards/{{id}}" Friday, October

    7, 11
  24. When /^I send a GET request to "([^"]*)"$/ do |path|

    path = Mustache.render(path, {:id => @id}) get path end Friday, October 7, 11
  25. 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
  26. Verification of Response Data Friday, October 7, 11

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

  28. 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
  29. What about our old friend, XML? Friday, October 7, 11

  30. Scenario: create a card with XML Given I send and

    accept XML When I send a POST request to "/cards" with the following: """ <?xml version="1.0"?> <card> <first_name>Anthony</first_name> <last_name>Eden</last_name> <display_ame>Anthony Eden</display_name> </card> """ Then the response status should be "201" And the response body should be an XML representation of the Card Friday, October 7, 11
  31. Given /^I send and accept XML$/ do header 'Accept', 'text/xml'

    header 'Content-Type', 'text/xml' end Friday, October 7, 11
  32. 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
  33. The Setup Friday, October 7, 11

  34. Rails Friday, October 7, 11

  35. gem 'rspec' gem 'cucumber-rails' gem 'database_cleaner' gem 'mustache' gem 'json_select'

    Friday, October 7, 11
  36. Sinatra Friday, October 7, 11

  37. env.rb Friday, October 7, 11

  38. class AddressBookWorld include RSpec::Expectations include RSpec::Matchers include Rack::Test::Methods def app

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

    memo } end end Friday, October 7, 11
  40. The Versioning Debate Friday, October 7, 11

  41. /v1/cards Friday, October 7, 11

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

  43. No Versioning Friday, October 7, 11

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

  45. HATEOAS Friday, October 7, 11

  46. "links": [ { "rel": "addresses", "uri": "/cards/1/addresses" }, { "rel":

    "self", "uri": "/cards/1" } ] Friday, October 7, 11
  47. <link rel=”addresses” uri=”/cards/1/addresses” /> <link rel=”self” uri=”/cards/1” /> Friday, October

    7, 11
  48. RSpec for Integration Friday, October 7, 11

  49. 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
  50. 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
  51. The Future of Web Applications Friday, October 7, 11

  52. Things to Think About Friday, October 7, 11

  53. How would building services as only APIs affect your choice

    of tools and techniques? Friday, October 7, 11
  54. Are there programming languages that would be better suited to

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

    October 7, 11
  56. Fuzz test your beliefs Friday, October 7, 11

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

  58. 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

  59. Friday, October 7, 11

  60. Friday, October 7, 11

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