Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add RSpec/NoExpectationExample #1321

Merged
merged 8 commits into from
Jul 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,13 @@ Naming/InclusiveLanguage:
Suggestions:
- offense

RSpec:
Language:
Expectations:
- expect_correction
- expect_no_offenses
- expect_offense

RSpec/ExampleLength:
CountAsOne:
- heredoc
Expand All @@ -81,6 +88,12 @@ RSpec/DescribeClass:
Exclude:
- spec/project/**/*.rb

RSpec/MultipleExpectations:
Max: 2
pirj marked this conversation as resolved.
Show resolved Hide resolved

RSpec/NoExpectationExample:
Enabled: true

Style/FormatStringToken:
Exclude:
- spec/rubocop/**/*.rb
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
* Add new `AllowConsecutiveOneLiners` (default true) option for `Rspec/EmptyLineAfterHook` cop. ([@ngouy][])
* Add autocorrect support for `RSpec/EmptyExampleGroup`. ([@r7kamura][])
* Fix `RSpec/ChangeByZero` with compound expressions using `&` or `|` operators. ([@BrianHawley][])
* Add `RSpec/NoExpectationExample`. ([@r7kamura][])
* Add some expectation methods to default configuration. ([@r7kamura][])

## 2.12.1 (2022-07-03)

Expand Down
14 changes: 13 additions & 1 deletion config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,14 @@ RSpec:
Pending:
- pending
Expectations:
- are_expected
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change in Language potentially affects multiple cops (ExpectInHook, MultipleExpectations. StubbedMock). Perhaps it's worth mentioning it in the changelog. Also I'm not sure we should support the should expectations at all

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bquorning what do you think about extending the default language?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven’t personally used the ‘should’ syntax since RSpec 3 was released, but from the RSpec documentation it sounds like it’s not being removed, even in v4. So I guess we should support it?

@pirj I imagine you might know about official RSpec plans?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All forms of should will be removed from RSpec 4 with no replacement.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I would not keep the burden of supporting should, as it was soft-deprecated nine years ago and was disabled in the generated config since then:

For RSpec 3, we considered the idea of disabling the old syntax by default, forcing users to opt-in to use it.

But I still see people use it, even those who joined software development less than nine years ago 😄

- expect
- is_expected
- expect_any_instance_of
- is_expected
- should
- should_not
- should_not_receive
- should_receive
Helpers:
- let
- let!
Expand Down Expand Up @@ -614,6 +619,13 @@ RSpec/NestedGroups:
VersionChanged: '1.10'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups

RSpec/NoExpectationExample:
Description: Checks if an example contains any expectation.
Enabled: pending
Safe: false
VersionAdded: '2.13'
Reference: https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample

RSpec/NotToNot:
Description: Checks for consistent method usage for negating expectations.
Enabled: true
Expand Down
1 change: 1 addition & 0 deletions docs/modules/ROOT/pages/cops.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
* xref:cops_rspec.adoc#rspecmultiplesubjects[RSpec/MultipleSubjects]
* xref:cops_rspec.adoc#rspecnamedsubject[RSpec/NamedSubject]
* xref:cops_rspec.adoc#rspecnestedgroups[RSpec/NestedGroups]
* xref:cops_rspec.adoc#rspecnoexpectationexample[RSpec/NoExpectationExample]
* xref:cops_rspec.adoc#rspecnottonot[RSpec/NotToNot]
* xref:cops_rspec.adoc#rspecoverwritingsetup[RSpec/OverwritingSetup]
* xref:cops_rspec.adoc#rspecpending[RSpec/Pending]
Expand Down
45 changes: 45 additions & 0 deletions docs/modules/ROOT/pages/cops_rspec.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -3284,6 +3284,51 @@ end

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NestedGroups

== RSpec/NoExpectationExample

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Pending
| No
| No
| 2.13
| -
|===

Checks if an example contains any expectation.

All RSpec's example and expectation methods are covered by default.
If you are using your own custom methods,
add the following configuration:

RSpec:
Language:
Examples:
Regular:
- custom_it
Expectations:
- custom_expect

=== Examples

[source,ruby]
----
# bad
it do
a?
end

# good
it do
expect(a?).to be(true)
end
----

=== References

* https://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/NoExpectationExample

== RSpec/NotToNot

|===
Expand Down
60 changes: 60 additions & 0 deletions lib/rubocop/cop/rspec/no_expectation_example.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Checks if an example contains any expectation.
#
# All RSpec's example and expectation methods are covered by default.
# If you are using your own custom methods,
# add the following configuration:
#
# RSpec:
# Language:
# Examples:
# Regular:
# - custom_it
# Expectations:
# - custom_expect
#
# @example
#
# # bad
# it do
# a?
# end
#
# # good
# it do
# expect(a?).to be(true)
# end
class NoExpectationExample < Base
MSG = 'No expectation found in this example.'

# @!method regular_or_focused_example?(node)
# @param [RuboCop::AST::Node] node
# @return [Boolean]
def_node_matcher(
:regular_or_focused_example?,
block_pattern('{#Examples.regular | #Examples.focused}')
)

# @!method including_any_expectation?(node)
# @param [RuboCop::AST::Node] node
# @return [Boolean]
def_node_search(
:including_any_expectation?,
send_pattern('#Expectations.all')
)

# @param [RuboCop::AST::BlockNode] node
def on_block(node)
return unless regular_or_focused_example?(node)
return if including_any_expectation?(node)

add_offense(node)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
require_relative 'rspec/multiple_subjects'
require_relative 'rspec/named_subject'
require_relative 'rspec/nested_groups'
require_relative 'rspec/no_expectation_example'
require_relative 'rspec/not_to_not'
require_relative 'rspec/overwriting_setup'
require_relative 'rspec/pending'
Expand Down
117 changes: 117 additions & 0 deletions spec/rubocop/cop/rspec/no_expectation_example_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::NoExpectationExample do
context 'with no expectation example with it' do
it 'registers an offense' do
expect_offense(<<~RUBY)
RSpec.describe Foo do
it { bar }
^^^^^^^^^^ No expectation found in this example.

it { expect(baz).to be_truthy }
end
RUBY
end
end

context 'with no expectation example with specify' do
it 'registers an offense' do
expect_offense(<<~RUBY)
specify { bar }
^^^^^^^^^^^^^^^ No expectation found in this example.
RUBY
end
end

context 'with expectation example with should' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
it { should be_truthy }
RUBY
end
end

context 'with multi no expectation examples' do
it 'registers offenses' do
expect_offense(<<~RUBY)
it { bar }
^^^^^^^^^^ No expectation found in this example.
it { baz }
^^^^^^^^^^ No expectation found in this example.
RUBY
end
end

context 'with custom expectation example' do
it 'registers an offense' do
expect_offense(<<~RUBY)
it { custom_expect(bar) }
^^^^^^^^^^^^^^^^^^^^^^^^^ No expectation found in this example.
RUBY
end
end

context 'with configured custom expectation example' do
before do
other_cops.dig('RSpec', 'Language', 'Expectations').push('custom_expect')
end

it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
it { custom_expect(bar) }
RUBY
end
end

context 'with no expectation custom example' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
custom_it { foo }
RUBY
end
end

context 'with no expectation configured custom example' do
before do
other_cops.dig(
'RSpec',
'Language',
'Examples',
'Regular'
).push('custom_it')
end

it 'registers an offense' do
expect_offense(<<~RUBY)
custom_it { foo }
^^^^^^^^^^^^^^^^^ No expectation found in this example.
RUBY
end
end

context 'with block-less example in block' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
RSpec.describe Foo do
it 'not implemented'
end
RUBY
end
end

context 'with no expectation pending example' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
pending { bar }
RUBY
end
end

context 'with no expectation skipped example' do
it 'registers no offenses' do
expect_no_offenses(<<~RUBY)
skip { bar }
RUBY
end
end
end