diff --git a/.rubocop.yml b/.rubocop.yml index bc7a90bf4..d2feab940 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -107,3 +107,5 @@ RSpec/FactoryBot/SyntaxMethods: Enabled: true RSpec/Rails/AvoidSetupHook: Enabled: true +RSpec/Rails/HaveHttpStatus: + Enabled: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 66d4aa07a..a215c60b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * Fix error in `RSpec/RSpec/FactoryBot/CreateList` cop for empty block. ([@tejasbubane][]) * Update `RSpec/MultipleExpectations` cop documentation with examples of aggregate_failures use. ([@edgibbs][]) * Declare autocorrect as unsafe for `RSpec/VerifiedDoubleReference`. ([@Drowze][]) +* Add new `RSpec/Rails/HaveHttpStatus` cop. ([@akiomik][]) ## 2.11.1 (2022-05-18) @@ -706,3 +707,4 @@ Compatibility release so users can upgrade RuboCop to 0.51.0. No new features. [@ngouy]: https://github.com/ngouy [@edgibbs]: https://github.com/edgibbs [@Drowze]: https://github.com/Drowze +[@akiomik]: https://github.com/akiomik diff --git a/config/default.yml b/config/default.yml index 51b998412..92a63b3d8 100644 --- a/config/default.yml +++ b/config/default.yml @@ -909,6 +909,13 @@ RSpec/Rails/AvoidSetupHook: VersionAdded: '2.4' Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/AvoidSetupHook +RSpec/Rails/HaveHttpStatus: + Description: Checks that tests use `have_http_status` instead of equality matchers. + Enabled: pending + SafeAutoCorrect: false + VersionAdded: "<>" + Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HaveHttpStatus + RSpec/Rails/HttpStatus: Description: Enforces use of symbolic or numeric value to describe HTTP status. Enabled: true diff --git a/docs/modules/ROOT/pages/cops.adoc b/docs/modules/ROOT/pages/cops.adoc index 641fa64a7..120de28e8 100644 --- a/docs/modules/ROOT/pages/cops.adoc +++ b/docs/modules/ROOT/pages/cops.adoc @@ -106,6 +106,7 @@ === Department xref:cops_rspec_rails.adoc[RSpec/Rails] * xref:cops_rspec_rails.adoc#rspecrails/avoidsetuphook[RSpec/Rails/AvoidSetupHook] +* xref:cops_rspec_rails.adoc#rspecrails/havehttpstatus[RSpec/Rails/HaveHttpStatus] * xref:cops_rspec_rails.adoc#rspecrails/httpstatus[RSpec/Rails/HttpStatus] // END_COP_LIST diff --git a/docs/modules/ROOT/pages/cops_rspec_rails.adoc b/docs/modules/ROOT/pages/cops_rspec_rails.adoc index d23035d55..e42579288 100644 --- a/docs/modules/ROOT/pages/cops_rspec_rails.adoc +++ b/docs/modules/ROOT/pages/cops_rspec_rails.adoc @@ -33,6 +33,35 @@ end * https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/AvoidSetupHook +== RSpec/Rails/HaveHttpStatus + +|=== +| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed + +| Pending +| Yes +| Yes (Unsafe) +| <> +| - +|=== + +Checks that tests use `have_http_status` instead of equality matchers. + +=== Examples + +[source,ruby] +---- +# bad +expect(response.status).to be(200) + +# good +expect(response).to have_http_status(200) +---- + +=== References + +* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/Rails/HaveHttpStatus + == RSpec/Rails/HttpStatus |=== diff --git a/lib/rubocop/cop/rspec/rails/have_http_status.rb b/lib/rubocop/cop/rspec/rails/have_http_status.rb new file mode 100644 index 000000000..780dddb31 --- /dev/null +++ b/lib/rubocop/cop/rspec/rails/have_http_status.rb @@ -0,0 +1,47 @@ +# frozen_string_literal: true + +module RuboCop + module Cop + module RSpec + module Rails + # Checks that tests use `have_http_status` instead of equality matchers. + # + # @example + # # bad + # expect(response.status).to be(200) + # + # # good + # expect(response).to have_http_status(200) + # + class HaveHttpStatus < Base + extend AutoCorrector + + MSG = + 'Prefer `expect(response).%s have_http_status(%i)` ' \ + 'over `expect(response.status).%s %s`.' + + # @!method match_status(node) + def_node_matcher :match_status, <<-PATTERN + (send + (send nil? :expect + $(send (send nil? :response) :status) + ) + $#Runners.all + $(send nil? {:be :eq :eql :equal} (int $_)) + ) + PATTERN + + def on_send(node) + match_status(node) do |response_status, to, match, status| + message = format(MSG, to: to, match: match.source, status: status) + add_offense(node, message: message) do |corrector| + corrector.replace(response_status.source_range, 'response') + corrector.replace(match.loc.selector, 'have_http_status') + end + end + end + end + end + end + end +end diff --git a/lib/rubocop/cop/rspec_cops.rb b/lib/rubocop/cop/rspec_cops.rb index 5ba81344e..3ce86db8b 100644 --- a/lib/rubocop/cop/rspec_cops.rb +++ b/lib/rubocop/cop/rspec_cops.rb @@ -11,6 +11,7 @@ require_relative 'rspec/factory_bot/syntax_methods' require_relative 'rspec/rails/avoid_setup_hook' +require_relative 'rspec/rails/have_http_status' begin require_relative 'rspec/rails/http_status' rescue LoadError diff --git a/spec/rubocop/cop/rspec/rails/have_http_status_spec.rb b/spec/rubocop/cop/rspec/rails/have_http_status_spec.rb new file mode 100644 index 000000000..1f031863f --- /dev/null +++ b/spec/rubocop/cop/rspec/rails/have_http_status_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +RSpec.describe RuboCop::Cop::RSpec::Rails::HaveHttpStatus do + it 'registers an offense for `expect(response.status).to be(200)`' do + expect_offense(<<~RUBY) + it { expect(response.status).to be(200) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `expect(response).to have_http_status(200)` over `expect(response.status).to be(200)`. + RUBY + + expect_correction(<<~RUBY) + it { expect(response).to have_http_status(200) } + RUBY + end + + it 'registers an offense for `expect(response.status).not_to eq(404)`' do + expect_offense(<<~RUBY) + it { expect(response.status).not_to eq(404) } + ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Prefer `expect(response).not_to have_http_status(404)` over `expect(response.status).not_to eq(404)`. + RUBY + + expect_correction(<<~RUBY) + it { expect(response).not_to have_http_status(404) } + RUBY + end + + it 'does not register an offense for `is_expected.to be(200)`' do + expect_no_offenses(<<~RUBY) + it { is_expected.to be(200) } + RUBY + end + + it 'does not register an offense for `expect(res.status).to be(200)`' do + expect_no_offenses(<<~RUBY) + it { expect(res.status).to be(200) } + RUBY + end +end