Skip to content

Commit

Permalink
Check that stubs aren't added to mocks
Browse files Browse the repository at this point in the history
  • Loading branch information
bquorning committed Jan 1, 2019
1 parent cf504a0 commit 3ab2326
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 0 deletions.
5 changes: 5 additions & 0 deletions config/default.yml
Expand Up @@ -283,6 +283,11 @@ RSpec/MissingExampleGroupArgument:
Enabled: true
StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument

RSpec/MockNotStub:
Description: Checks that stubs aren't added to mocks.
Enabled: true
StyleGuide: http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MockNotStub

RSpec/MultipleDescribes:
Description: Checks for multiple top level describes.
Enabled: true
Expand Down
73 changes: 73 additions & 0 deletions lib/rubocop/cop/rspec/mock_not_stub.rb
@@ -0,0 +1,73 @@
# frozen_string_literal: true

module RuboCop
module Cop
module RSpec
# Checks that stubs aren't added to mocks.
#
# @example
#
# # bad
# expect(foo).to receive(:bar).with(42).and_return("hello world")
#
# # good (without spies)
# allow(foo).to receive(:bar).with(42).and_return("hello world")
# expect(foo).to receive(:bar).with(42)
#
# # good (with spies)
# allow(foo).to receive(:bar).with(42).and_return("hello world")
# expect(foo).to have_received(:bar).with(42)
#
class MockNotStub < Cop
MSG = "Don't stub your mock.".freeze

def_node_matcher :message_expectation_with_return_argument, <<-PATTERN
(send
(send nil? :expect ...) :to
$(send #receive :and_return _)
)
PATTERN

def_node_matcher :message_expectation_with_return_block, <<-PATTERN
(send
(send nil? :expect ...) :to
$(block #receive (args) _)
)
PATTERN

def_node_matcher :receive, <<-PATTERN
{
(send nil? :receive ...)
(send (send nil? :receive ...) :with ...)
}
PATTERN

def on_send(node)
message_expectation_with_return_argument(node) do |match|
add_offense(match, location: offending_argument_range(match.loc))
end

message_expectation_with_return_block(node) do |match|
add_offense(match, location: offending_block_range(match.loc))
end
end

def offending_argument_range(source_map)
Parser::Source::Range.new(
source_map.expression.source_buffer,
source_map.dot.begin_pos,
source_map.end.end_pos
)
end

def offending_block_range(source_map)
Parser::Source::Range.new(
source_map.expression.source_buffer,
source_map.begin.begin_pos,
source_map.end.end_pos
)
end
end
end
end
end
1 change: 1 addition & 0 deletions lib/rubocop/cop/rspec_cops.rb
Expand Up @@ -52,6 +52,7 @@
require_relative 'rspec/message_expectation'
require_relative 'rspec/message_spies'
require_relative 'rspec/missing_example_group_argument'
require_relative 'rspec/mock_not_stub'
require_relative 'rspec/multiple_describes'
require_relative 'rspec/multiple_expectations'
require_relative 'rspec/multiple_subjects'
Expand Down
1 change: 1 addition & 0 deletions manual/cops.md
Expand Up @@ -53,6 +53,7 @@
* [RSpec/MessageExpectation](cops_rspec.md#rspecmessageexpectation)
* [RSpec/MessageSpies](cops_rspec.md#rspecmessagespies)
* [RSpec/MissingExampleGroupArgument](cops_rspec.md#rspecmissingexamplegroupargument)
* [RSpec/MockNotStub](cops_rspec.md#rspecmocknotstub)
* [RSpec/MultipleDescribes](cops_rspec.md#rspecmultipledescribes)
* [RSpec/MultipleExpectations](cops_rspec.md#rspecmultipleexpectations)
* [RSpec/MultipleSubjects](cops_rspec.md#rspecmultiplesubjects)
Expand Down
27 changes: 27 additions & 0 deletions manual/cops_rspec.md
Expand Up @@ -1633,6 +1633,33 @@ end

* [http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument](http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MissingExampleGroupArgument)

## RSpec/MockNotStub

Enabled by default | Supports autocorrection
--- | ---
Enabled | No

Checks that stubs aren't added to mocks.

### Examples

```ruby
# bad
expect(foo).to receive(:bar).with(42).and_return("hello world")

# good (without spies)
allow(foo).to receive(:bar).with(42).and_return("hello world")
expect(foo).to receive(:bar).with(42)

# good (with spies)
allow(foo).to receive(:bar).with(42).and_return("hello world")
expect(foo).to have_received(:bar).with(42)
```

### References

* [http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MockNotStub](http://www.rubydoc.info/gems/rubocop-rspec/RuboCop/Cop/RSpec/MockNotStub)

## RSpec/MultipleDescribes

Enabled by default | Supports autocorrection
Expand Down
37 changes: 37 additions & 0 deletions spec/rubocop/cop/rspec/mock_not_stub_spec.rb
@@ -0,0 +1,37 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::RSpec::MockNotStub do
subject(:cop) { described_class.new }

it 'flags expect(...).to receive(...).with(...).and_return' do
expect_offense(<<-RUBY)
expect(foo).to receive(:bar).with(42).and_return("hello world")
^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't stub your mock.
RUBY
end

it 'flags expect(...).to receive(...).with(...) { } ' do
expect_offense(<<-RUBY)
expect(foo).to receive(:bar).with(42) { "hello world" }
^^^^^^^^^^^^^^^^^ Don't stub your mock.
RUBY
end

it 'flags expect(...).to receive(...).and_return' do
expect_offense(<<-RUBY)
expect(foo).to receive(:bar).and_return("hello world")
^^^^^^^^^^^^^^^^^^^^^^^^^^ Don't stub your mock.
RUBY
end

it 'flags expect(...).to receive(...) { } ' do
expect_offense(<<-RUBY)
expect(foo).to receive(:bar) { "hello world" }
^^^^^^^^^^^^^^^^^ Don't stub your mock.
RUBY
end

it 'approves of expect(...).to have_received' do
expect_no_offenses('expect(foo).to have_received(:bar)')
end
end

0 comments on commit 3ab2326

Please sign in to comment.