From 002ca1ab76191bcecae0198d272ec8af7a132203 Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 2 Aug 2023 19:12:21 +0200 Subject: [PATCH 1/2] Refactor to make room for be_an_http_gem_response.with(:status) --- lib/http/support/rspec_matchers.rb | 44 ++++++++++++++++++------------ 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/lib/http/support/rspec_matchers.rb b/lib/http/support/rspec_matchers.rb index ed7755f..0ea0c9c 100644 --- a/lib/http/support/rspec_matchers.rb +++ b/lib/http/support/rspec_matchers.rb @@ -32,25 +32,22 @@ def expected_code match do |actual| @actual = actual - return false if invalid_response_type_message + match_response_type && match_status_code + end + + def match_response_type = @actual.is_a?(HTTP::Response) + def match_status_code case expected_code when Integer - expected_code == actual.status.code + expected_code == @actual.status.code when Range - expected_code.cover?(actual.status.code) + expected_code.cover?(@actual.status.code) else raise "Unknown expected code #{expected_code}. Please report this as an issue" end end - def invalid_response_type_message - return if @actual.is_a? HTTP::Response - - "expected a HTTP::Response object, but an instance of " \ - "#{@actual.class} was received" - end - def status_code_to_name(code) STATUS_CODE_TO_SYMBOL.fetch(code, "unkown name") end @@ -63,22 +60,33 @@ def expected_type end end - def actual_type - "#{actual.status.code} #{status_code_to_name(actual.status.code).inspect}" - end - description do "respond with #{expected_type}" end + def invalid_response_type_message + "expected a HTTP::Response object, but an instance of " \ + "#{@actual.class} was received" + end + + def actual_type + "#{actual.status.code} #{status_code_to_name(actual.status.code).inspect}" + end + def failure_message - invalid_response_type_message || - "expected the response to have #{expected_type} but it was #{actual_type}" + return invalid_response_type_message unless match_response_type + + return "expected the response to have #{expected_type} but it was #{actual_type}" unless match_status_code + + "unknown reason why it fails, please report it" end def failure_message_when_negated - invalid_response_type_message || - "expected the response not to have #{expected_type} but it was #{actual_type}" + return invalid_response_type_message unless match_response_type + + return "expected the response not to have #{expected_type} but it was #{actual_type}" if match_status_code + + "unknown reason why it fails, please report it" end end end From 1cb6d059af5190f616520501270ca0cbe1fc6bbe Mon Sep 17 00:00:00 2001 From: Simon Date: Wed, 2 Aug 2023 19:32:57 +0200 Subject: [PATCH 2/2] Rename matcher to be_an_http_gem_response It reads a bit better and allow extending quite easily. --- .rubocop.yml | 3 ++ README.md | 16 +++--- lib/http/rspec.rb | 2 +- lib/http/support/rspec_matchers.rb | 26 +++++++--- spec/http/support/rspec_matchers_spec.rb | 63 +++++++++++++++--------- 5 files changed, 72 insertions(+), 38 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index dab5d10..8f5b43a 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -16,6 +16,9 @@ Metrics/BlockLength: Exclude: - spec/**/*.rb +Metrics/MethodLength: + Max: 20 + RSpec/MultipleExpectations: Max: 10 RSpec/ExampleLength: diff --git a/README.md b/README.md index a914433..7843c90 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ RSpec.describe Service do include HTTP::Support::RspecMatchers it "makes request" do - expect(response).to have_httprb_status(200) + expect(response).to be_an_http_gem_response.with(status: 200) end end ``` @@ -62,14 +62,14 @@ Here's some simple examples to get you started: ```ruby it "has successful response" do response = HTTP.get("www.nrk.no") - expect(response).to have_httprb_status(:success) # will match 2xx status code - expect(response).to have_httprb_status(:redirect) # will match 3xx status code - expect(response).to have_httprb_status(:error) # will match 3xx status code + expect(response).to be_an_http_gem_response.with(status: :success) # will match 2xx status code + expect(response).to be_an_http_gem_response.with(status: :redirect) # will match 3xx status code + expect(response).to be_an_http_gem_response.with(status: :error) # will match 3xx status code - expect(response).to have_httprb_status(:ok) # require 200 status code - expect(response).to have_httprb_status(200) # require 200 status code - expect(response).to have_httprb_status(:not_found) # require 404 status code - expect(response).to have_httprb_status(404) # require 404 status code + expect(response).to be_an_http_gem_response.with(status: :ok) # require 200 status code + expect(response).to be_an_http_gem_response.with(status: 200) # require 200 status code + expect(response).to be_an_http_gem_response.with(status: :not_found) # require 404 status code + expect(response).to be_an_http_gem_response.with(status: 404) # require 404 status code # you can access HTTP::Support::RspecMatchers::STATUS_CODE_TO_SYMBOL to see the full # mapping between code and symbol diff --git a/lib/http/rspec.rb b/lib/http/rspec.rb index 3627a0c..e470011 100644 --- a/lib/http/rspec.rb +++ b/lib/http/rspec.rb @@ -1,3 +1,3 @@ # frozen_string_literal: true -require_relative "support/support/matchers" +require_relative "support/matchers" diff --git a/lib/http/support/rspec_matchers.rb b/lib/http/support/rspec_matchers.rb index 0ea0c9c..c85c019 100644 --- a/lib/http/support/rspec_matchers.rb +++ b/lib/http/support/rspec_matchers.rb @@ -15,17 +15,27 @@ module RspecMatchers end STATUS_SYMBOL_TO_CODE = STATUS_CODE_TO_SYMBOL.invert - matcher :have_httprb_status do |_expected| # rubocop:disable Metrics/BlockLength + matcher :be_an_http_gem_response do |_expected| # rubocop:disable Metrics/BlockLength + chain :with do |options| + if options[:status] + raise ArgumentError, "status is all ready passed in" if @expected_status + + @expected_status = options[:status] + end + end + def expected_code - case expected - when Integer then expected + case @expected_status + when Integer then @expected_status when :success, :successful then 200..299 when :redirect then 300..399 when :error then 500..599 when Symbol - STATUS_SYMBOL_TO_CODE.fetch(expected) { raise ArgumentError, "unknown symbol #{expected.inspect}" } + STATUS_SYMBOL_TO_CODE.fetch(@expected_status) do + raise ArgumentError, "unknown symbol #{@expected_status.inspect}" + end else - raise ArgumentError, "unknown expected value. Should be either a Integer or a symbol" + raise ArgumentError, "unknown status value. Should be either a Integer or a symbol" end end @@ -38,6 +48,9 @@ def expected_code def match_response_type = @actual.is_a?(HTTP::Response) def match_status_code + # without @expected_status we dont have anything to compare against + return true unless @expected_status + case expected_code when Integer expected_code == @actual.status.code @@ -61,7 +74,8 @@ def expected_type end description do - "respond with #{expected_type}" + "http gem respond" + .then { @expected_status ? "#{_1} with #{expected_type}" : _1 } end def invalid_response_type_message diff --git a/spec/http/support/rspec_matchers_spec.rb b/spec/http/support/rspec_matchers_spec.rb index 5231453..d2e57f3 100644 --- a/spec/http/support/rspec_matchers_spec.rb +++ b/spec/http/support/rspec_matchers_spec.rb @@ -7,26 +7,35 @@ RSpec.describe HTTP::Support::RspecMatchers do include described_class - describe "have_httprb_status" do + describe "be_an_http_gem_response" do it "raises error if it gets unexpected argument" do - matcher = have_httprb_status(200) + matcher = be_an_http_gem_response expect(matcher.matches?("response")).to be(false) expect(matcher.failure_message) .to eq("expected a HTTP::Response object, but an instance of String was received") end - it "works with description for 200 ok" do - matcher = have_httprb_status(200) + it "description with no other constrains" do + matcher = be_an_http_gem_response stub_request(:get, "https://nrk.no/").to_return(:status => 200) response = HTTP.get("https://nrk.no") expect(matcher.matches?(response)).to be(true) - expect(matcher.description).to eq("respond with 200 :ok") + expect(matcher.description).to eq("http gem respond") + end + + it "description include status" do + matcher = be_an_http_gem_response.with(:status => 200) + stub_request(:get, "https://nrk.no/").to_return(:status => 200) + + response = HTTP.get("https://nrk.no") + expect(matcher.matches?(response)).to be(true) + expect(matcher.description).to eq("http gem respond with 200 :ok") end it "has reasonable failure message for 200 ok failure" do - matcher = have_httprb_status(200) + matcher = be_an_http_gem_response.with(:status => 200) stub_request(:get, "https://nrk.no/").to_return(:status => 400) response = HTTP.get("https://nrk.no") @@ -36,7 +45,7 @@ end it "has reasonable description for negated 200 ok failure" do - matcher = have_httprb_status(200) + matcher = be_an_http_gem_response.with(:status => 200) stub_request(:get, "https://nrk.no/").to_return(:status => 200) response = HTTP.get("https://nrk.no") @@ -54,7 +63,7 @@ it "raises for unknown symbol" do expect do - matcher = have_httprb_status(:ruby) + matcher = be_an_http_gem_response.with(:status => :ruby) matcher.matches?(response) end .to raise_error(ArgumentError, "unknown symbol :ruby") @@ -62,78 +71,78 @@ it "raises for wrong type" do expect do - matcher = have_httprb_status("Coty") + matcher = be_an_http_gem_response.with(:status => "Coty") matcher.matches?(response) end - .to raise_error(ArgumentError, "unknown expected value. Should be " \ + .to raise_error(ArgumentError, "unknown status value. Should be " \ "either a Integer or a symbol") end it "can take :continue and convert in into 100" do - matcher = have_httprb_status(:continue) + matcher = be_an_http_gem_response.with(:status => :continue) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 100 :continue but/) end it "can take :switching_protocols and convert in into 101" do - matcher = have_httprb_status(:switching_protocols) + matcher = be_an_http_gem_response.with(:status => :switching_protocols) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 101 :switching_protocols but/) end it "can take :ok and convert in into 200" do - matcher = have_httprb_status(:ok) + matcher = be_an_http_gem_response.with(:status => :ok) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 200 :ok but/) end it "can take :created and convert in into 201" do - matcher = have_httprb_status(:created) + matcher = be_an_http_gem_response.with(:status => :created) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 201 :created but/) end it "can take :non_authoritative_information and convert in into 203" do - matcher = have_httprb_status(:non_authoritative_information) + matcher = be_an_http_gem_response.with(:status => :non_authoritative_information) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 203 :non_authoritative_information but/) end it "can take :multi_status and convert in into 207" do - matcher = have_httprb_status(:multi_status) + matcher = be_an_http_gem_response.with(:status => :multi_status) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 207 :multi_status but/) end it "can take :not_found and convert in into 404" do - matcher = have_httprb_status(:not_found) + matcher = be_an_http_gem_response.with(:status => :not_found) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 404 :not_found but/) end it "can take :uri_too_long and convert in into 414" do - matcher = have_httprb_status(:uri_too_long) + matcher = be_an_http_gem_response.with(:status => :uri_too_long) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 414 :uri_too_long but/) end it "can take :internal_server_error and convert in into 500" do - matcher = have_httprb_status(:internal_server_error) + matcher = be_an_http_gem_response.with(:status => :internal_server_error) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 500 :internal_server_error but/) end it "can take :gateway_timeout and convert in into 504" do - matcher = have_httprb_status(:gateway_timeout) + matcher = be_an_http_gem_response.with(:status => :gateway_timeout) expect(matcher.matches?(response)).to be(false) expect(matcher.failure_message) .to match(/expected the response to have 504 :gateway_timeout but/) @@ -142,7 +151,7 @@ %i[success successful].each do |success_name| it "accepts #{success_name} for all 2xx codes" do - matcher = have_httprb_status(success_name) + matcher = be_an_http_gem_response.with(:status => success_name) stub_request(:get, "https://nrk.no/").to_return(:status => 200) response = HTTP.get("https://nrk.no") @@ -166,7 +175,7 @@ end it "accepts :redirect for all 3xx codes" do - matcher = have_httprb_status(:redirect) + matcher = be_an_http_gem_response.with(:status => :redirect) stub_request(:get, "https://nrk.no/").to_return(:status => 300) response = HTTP.get("https://nrk.no") @@ -189,7 +198,7 @@ end it "accepts :error for all 5xx codes" do - matcher = have_httprb_status(:error) + matcher = be_an_http_gem_response.with(:status => :error) stub_request(:get, "https://nrk.no/").to_return(:status => 500) response = HTTP.get("https://nrk.no") @@ -211,4 +220,12 @@ .to eq("expected the response to have 500..599 code but it was 400 :bad_request") end end + + it "raises if you call it with twice with status" do + expect do + matcher = be_an_http_gem_response.with(:status => 200).with(:status => 300) + matcher.matches?(response) + end + .to raise_error(ArgumentError, "status is all ready passed in") + end end