diff --git a/.rvmrc b/.rvmrc deleted file mode 100644 index 965ca62..0000000 --- a/.rvmrc +++ /dev/null @@ -1 +0,0 @@ -rvm use 1.9.3@cucumber-api-steps diff --git a/.travis.yml b/.travis.yml index a5b8f0f..fc757bd 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,8 @@ language: ruby rvm: - - "1.9.3" - - "2.0.0" - - "2.1.2" - - jruby-19mode # JRuby in 1.9 mode + - 2.2.8 + - 2.3.5 + - 2.4.2 + - 2.5.1 - jruby-20mode # JRuby in 1.9 mode + - ruby-head diff --git a/LICENSE.TXT b/LICENSE.TXT new file mode 100644 index 0000000..779f8b4 --- /dev/null +++ b/LICENSE.TXT @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Ello PBC + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 5e9acaf..61e2e72 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,11 @@ [![Build Status](https://travis-ci.org/jayzes/cucumber-api-steps.png)](https://travis-ci.org/jayzes/cucumber-api-steps) [![Gem Version](https://badge.fury.io/rb/cucumber-api-steps.png)](http://badge.fury.io/rb/cucumber-api-steps) -A set of [Cucumber](https://github.com/aslakhellesoy/cucumber) step definitions utilizing +A set of [Cucumber](https://github.com/cucumber/cucumber) step definitions utilizing [Rack-Test](https://github.com/brynary/rack-test) that ease basic testing of REST-style APIs using either XML or JSON formats. -Adapted from [a blog post by Anthony Eden](http://www.anthonyeden.com/2010/11/testing-rest-apis-with-cucumber-and-rack-test/) with a few additions based on my own needs. I found myself copying these step definitions around to multiple projects, and decided that it would be worthwhile to gem them up to keep things nice and DRY. +Adapted from [a blog post by Anthony Eden](http://anthonyeden.com/2013/07/10/testing-rest-apis-with-cucumber-and-rack.html) with a few additions based on my own needs. I found myself copying these step definitions around to multiple projects, and decided that it would be worthwhile to gem them up to keep things nice and DRY. ## Dependencies @@ -17,60 +17,64 @@ Requires [Cucumber](https://github.com/aslakhellesoy/cucumber) (obviously). Als Add the following line to your Gemfile, preferably in the test or cucumber group: - gem 'cucumber-api-steps', :require => false +```ruby +gem 'cucumber-api-steps', :require => false +``` Then add the following line to your env.rb to make the step definitions available in your features: - require 'cucumber/api_steps' +```ruby +require 'cucumber/api_steps' +``` # Usage Still a work in progress. For now, read the api_steps.rb file or check out the [stashboard-rails](https://github.com/jayzes/stashboard-rails) project - its Cucumber features make extensive use of the steps in this gem. # Examples - - Feature: API - - Scenario: List tweets in JSON - When I send and accept JSON - And I send a GET request to "/api/tweets" - Then the response status should be "200" - And the JSON response should be: - """ - [{"tweet":"Hello World!"},{"tweet":"New Rails has been released"}] - """ - And the JSON response should have "$..tweet" with the text "Hello World!" - And the JSON response should have "$..tweet" with a length of 2 - - Scenario: List tweets in XML - When I send and accept XML - And I send a GET request to "/api/tweets" - Then the XML response should have "tweet" with text "Hello World!" - - Scenario: Post tweet using POST-params - When I send a POST request to "/api/tweets" with the following: - | tweet | Hello World! | - | lat | 42.848282 | - | lng | 74.634933 | - Then the response status should be "201" - - Scenario: Post tweet using json in POST body - When I send a POST request to "/api/tweets" with the following: - """ - {"tweet":"Hello World!","lat":"42.848282", "lng":"74.634933"} - """ - Then the response status should be "201" - - Scenario: Basic authentication - When I authenticate as the user "joe" with the password "password123" - And I send a GET request to "/api/tweets" - Then the response status should be "200" - - Scenario: Digest authentication - When I digest-authenticate as the user "joe" with the password "password123" - And I send a GET request to "/api/tweets" - Then the response status should be "200" - +```cucumber +Feature: API + + Scenario: List tweets in JSON + When I send and accept JSON + And I send a GET request to "/api/tweets" + Then the response status should be "200" + And the JSON response should be: + """ + [{"tweet":"Hello World!"},{"tweet":"New Rails has been released"}] + """ + And the JSON response should have "$..tweet" with the text "Hello World!" + And the JSON response should have "$..tweet" with a length of 2 + + Scenario: List tweets in XML + When I send and accept XML + And I send a GET request to "/api/tweets" + Then the XML response should have "tweet" with the text "Hello World!" + + Scenario: Post tweet using POST-params + When I send a POST request to "/api/tweets" with the following: + | tweet | Hello World! | + | lat | 42.848282 | + | lng | 74.634933 | + Then the response status should be "201" + + Scenario: Post tweet using json in POST body + When I send a POST request to "/api/tweets" with the following: + """ + {"tweet":"Hello World!","lat":"42.848282", "lng":"74.634933"} + """ + Then the response status should be "201" + + Scenario: Basic authentication + When I authenticate as the user "joe" with the password "password123" + And I send a GET request to "/api/tweets" + Then the response status should be "200" + + Scenario: Digest authentication + When I digest-authenticate as the user "joe" with the password "password123" + And I send a GET request to "/api/tweets" + Then the response status should be "200" +``` # Contributors * Jay Zeschin * Justin Smestad diff --git a/cucumber-api-steps.gemspec b/cucumber-api-steps.gemspec index 26cdcef..621f9e2 100644 --- a/cucumber-api-steps.gemspec +++ b/cucumber-api-steps.gemspec @@ -7,7 +7,7 @@ Gem::Specification.new do |s| s.version = Cucumber::ApiSteps::VERSION s.platform = Gem::Platform::RUBY s.authors = ["Jay Zeschin"] - s.email = ["jay.zeschin@modeset.com"] + s.email = ["jay@zeschin.org"] s.homepage = "http://github.com/jayzes/cucumber-api-steps" s.summary = %q{Cucumber steps to easily test REST-based XML and JSON APIs} s.description = %q{Cucumber steps to easily test REST-based XML and JSON APIs} @@ -15,9 +15,9 @@ Gem::Specification.new do |s| s.required_ruby_version = '>= 1.9.3' s.add_dependency 'jsonpath', '>= 0.1.2' - s.add_dependency 'cucumber', '>= 1.2.1' + s.add_dependency 'cucumber', '>= 2.0.2' s.add_development_dependency 'activesupport', '>= 3.0.0' - s.add_development_dependency 'rspec', '~> 2.12.0' + s.add_development_dependency 'rspec', '~> 3.3.0' s.add_development_dependency 'sinatra', '~> 1.4.3' s.files = `git ls-files`.split("\n") diff --git a/features/fixtures/fake_app.rb b/features/fixtures/fake_app.rb index e27f59c..3773de8 100644 --- a/features/fixtures/fake_app.rb +++ b/features/fixtures/fake_app.rb @@ -15,7 +15,7 @@ class FakeApp < Sinatra::Base if request.accept.empty? || request.accept?('application/json') content_type :json books.to_json - elsif request.accept?('application/xml') + elsif request.accept?('application/xml') content_type :xml books.to_xml end @@ -25,6 +25,10 @@ class FakeApp < Sinatra::Base status 201 if params.values == ["Metaprograming ruby", "Pragprog"] end + patch '/api/books' do + status 200 if params.values == ["Metaprograming ruby", "Pragprog"] + end + post '/api/publishers' do input_data = JSON.parse request.env["rack.input"].read, symbolize_names: true status 201 if input_data == {publisher: 'Pragprog'} diff --git a/features/request.feature b/features/request.feature index 866e7a4..2495eb8 100644 --- a/features/request.feature +++ b/features/request.feature @@ -16,6 +16,15 @@ Feature: """ Then the response status should be "201" + Scenario: PATCH request with params + When I perform the following step with table: + """ + I send a PATCH request to "/api/books" with the following: + | title | Metaprograming ruby | + | publisher | Pragprog | + """ + Then the response status should be "200" + Scenario: POST request with string When I perform the following step with string: """ diff --git a/features/response.feature b/features/response.feature index ea73612..716d44a 100644 --- a/features/response.feature +++ b/features/response.feature @@ -9,6 +9,15 @@ Feature: """ Then the response status should be "200" + + Scenario: Test response content type + And I perform the following step: + """ + I send and accept XML + I send a GET request to "/api/books" + """ + + Then the response content type should be XML Scenario: Test that JSON response contains a node When I perform the following step: diff --git a/features/step_definitions/api_test_steps.rb b/features/step_definitions/api_test_steps.rb index 31bb6e4..fc94e75 100644 --- a/features/step_definitions/api_test_steps.rb +++ b/features/step_definitions/api_test_steps.rb @@ -7,7 +7,7 @@ end Then /^the response should equal:$/ do |response_body| - last_response.body.should eq(response_body) + expect(last_response.body).to eq(response_body) end When /^I perform the following step with table:$/ do |step_definition| @@ -33,13 +33,13 @@ Then /^the request headers should be:$/ do |headers| headers_hash = headers.rows_hash request '/' - last_request.env.slice(*headers_hash.keys).values.should eq(headers_hash.values) + expect(last_request.env.slice(*headers_hash.keys).values).to eq(headers_hash.values) end Then /^I should be authenticated$/ do - last_request.env["HTTP_AUTHORIZATION"].should eq("Basic #{Base64.encode64("joe:god")}") + expect(last_request.env["HTTP_AUTHORIZATION"]).to eq("Basic #{Base64.strict_encode64("joe:god")}") end Then /^I should be digest authenticated$/ do - last_request.env["HTTP_AUTHORIZATION"].starts_with?("Digest ").should be_true + expect(last_request.env["HTTP_AUTHORIZATION"].starts_with?("Digest ")).to be true end diff --git a/lib/cucumber/api_steps.rb b/lib/cucumber/api_steps.rb index db26226..d2a0236 100644 --- a/lib/cucumber/api_steps.rb +++ b/lib/cucumber/api_steps.rb @@ -36,7 +36,7 @@ def to_str digest_authorize user, pass end -When /^I send a (GET|POST|PUT|DELETE) request (?:for|to) "([^"]*)"(?: with the following:)?$/ do |*args| +When /^I send a (GET|PATCH|POST|PUT|DELETE) request (?:for|to) "([^"]*)"(?: with the following:)?$/ do |*args| request_type = args.shift path = args.shift input = args.shift @@ -44,10 +44,10 @@ def to_str request_opts = {method: request_type.downcase.to_sym} unless input.nil? - if input.class == Cucumber::Ast::Table + if input.class == Cucumber::MultilineArgument::DataTable request_opts[:params] = input.rows_hash else - request_opts[:input] = input + request_opts[:input] = StringIO.new input end end @@ -69,21 +69,29 @@ def to_str end Then /^the response status should be "([^"]*)"$/ do |status| - if self.respond_to? :should - last_response.status.should == status.to_i + if self.respond_to?(:expect) + expect(last_response.status).to eq(status.to_i) else assert_equal status.to_i, last_response.status end end +Then(/^the response content type should be (XML|JSON)$/) do |type| + if self.respond_to? :should + last_response.content_type.should include("application/#{type.downcase}") + else + assert last_response.content_type.include?("application/#{type.downcase}") + end +end + Then /^the JSON response should (not)?\s?have "([^"]*)"$/ do |negative, json_path| json = JSON.parse(last_response.body) results = JsonPath.new(json_path).on(json).to_a.map(&:to_s) - if self.respond_to?(:should) + if self.respond_to?(:expect) if negative.present? - results.should be_empty + expect(results).to be_empty else - results.should_not be_empty + expect(results).not_to be_empty end else if negative.present? @@ -98,11 +106,11 @@ def to_str Then /^the JSON response should (not)?\s?have "([^"]*)" with the text "([^"]*)"$/ do |negative, json_path, text| json = JSON.parse(last_response.body) results = JsonPath.new(json_path).on(json).to_a.map(&:to_s) - if self.respond_to?(:should) + if self.respond_to?(:expect) if negative.present? - results.should_not include(text) + expect(results).not_to include(text) else - results.should include(text) + expect(results).to include(text) end else if negative.present? @@ -116,11 +124,11 @@ def to_str Then /^the XML response should (not)?\s?have "([^"]*)"$/ do |negative, xpath| parsed_response = Nokogiri::XML(last_response.body) elements = parsed_response.xpath(xpath) - if self.respond_to?(:should) + if self.respond_to?(:expect) if negative.present? - elements.should be_empty + expect(elements).to be_empty else - elements.should_not be_empty + expect(elements).not_to be_empty end else if negative.present? @@ -134,9 +142,9 @@ def to_str Then /^the XML response should have "([^"]*)" with the text "([^"]*)"$/ do |xpath, text| parsed_response = Nokogiri::XML(last_response.body) elements = parsed_response.xpath(xpath) - if self.respond_to?(:should) - elements.should_not be_empty, "could not find #{xpath} in:\n#{last_response.body}" - elements.find { |e| e.text == text }.should_not be_nil, "found elements but could not find #{text} in:\n#{elements.inspect}" + if self.respond_to?(:expect) + expect(elements).not_to be_empty, "could not find #{xpath} in:\n#{last_response.body}" + expect(elements.find { |e| e.text == text }).not_to be_nil, "found elements but could not find #{text} in:\n#{elements.inspect}" else assert !elements.empty?, "could not find #{xpath} in:\n#{last_response.body}" assert elements.find { |e| e.text == text }, "found elements but could not find #{text} in:\n#{elements.inspect}" @@ -147,18 +155,18 @@ def to_str expected = JSON.parse(json) actual = JSON.parse(last_response.body) - if self.respond_to?(:should) - actual.should == expected + if self.respond_to?(:expect) + expect(actual).to eq(expected) else - assert_equal actual, response + assert_equal expected, actual end end Then /^the JSON response should have "([^"]*)" with a length of (\d+)$/ do |json_path, length| json = JSON.parse(last_response.body) results = JsonPath.new(json_path).on(json) - if self.respond_to?(:should) - results.length.should == length.to_i + if self.respond_to?(:expect) + expect(results.length).to eq(length.to_i) else assert_equal length.to_i, results.length end diff --git a/lib/cucumber/api_steps/version.rb b/lib/cucumber/api_steps/version.rb index e0c3aee..15701e6 100644 --- a/lib/cucumber/api_steps/version.rb +++ b/lib/cucumber/api_steps/version.rb @@ -1,5 +1,5 @@ module Cucumber module ApiSteps - VERSION = "0.13" + VERSION = "0.14.0" end end