-
-
Notifications
You must be signed in to change notification settings - Fork 277
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
261 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
# frozen_string_literal: true | ||
|
||
module RuboCop | ||
module Cop | ||
module RSpec | ||
# Checks for stubs on test subjects | ||
# | ||
# @example | ||
# # bad | ||
# describe Foo do | ||
# subject(:bar) { baz } | ||
# | ||
# before do | ||
# allow(bar).to receive(:qux?).and_return(true) | ||
# end | ||
# end | ||
# | ||
class Stubject < Cop | ||
include RuboCop::RSpec::TopLevelDescribe, RuboCop::RSpec::Language | ||
|
||
MSG = 'Do not stub your test subject.'.freeze | ||
|
||
def_node_matcher :subject, <<-PATTERN | ||
(block (send nil :subject (sym $_)) args ...) | ||
PATTERN | ||
|
||
def_node_matcher :anonymous_subject?, <<-PATTERN | ||
(block (send nil :subject) args ...) | ||
PATTERN | ||
|
||
def_node_matcher :defines_subject?, <<-PATTERN | ||
(block (send nil :subject (sym [!%])) args ...) | ||
PATTERN | ||
|
||
def_node_matcher :subject_stub, '(send nil {:allow :spy} (send nil %))' | ||
def_node_matcher :subject_mock, <<-PATTERN | ||
(send | ||
(send nil :expect | ||
(send nil %)) :to | ||
{(send (send nil :receive ...) ...) (send nil :receive ...)}) | ||
PATTERN | ||
|
||
def_node_matcher :example_group?, <<-PATTERN | ||
(block | ||
(send _ {#{ExampleGroups::ALL.to_node_pattern}} ...) | ||
...) | ||
PATTERN | ||
|
||
def on_block(node) | ||
describe, = described_constant(node) | ||
return unless describe | ||
|
||
find_subject(node) do |subject_name, context| | ||
find_subject_stub(context, subject_name) do |stub| | ||
add_offense(stub, :expression) | ||
end | ||
end | ||
end | ||
|
||
private | ||
|
||
def find_subject_stub(context, subject_name, &block) | ||
return if example_group?(context) && defines_subject?(context) | ||
|
||
if subject_stub(context, subject_name) || subject_mock(context, subject_name) | ||
yield(context) | ||
end | ||
|
||
context.each_child_node do |node| | ||
find_subject_stub(node, subject_name, &block) | ||
end | ||
end | ||
|
||
def defines_subject?(node) | ||
node.each_child_node.any? do |child| | ||
subject(child) || | ||
anonymous_subject?(child) || | ||
defines_subject?(child) | ||
end | ||
end | ||
|
||
def find_subject(node, parent: nil, &block) | ||
subject(node) { |name| yield(name, parent) } | ||
|
||
node.each_child_node do |child| | ||
find_subject(child, parent: node, &block) | ||
end | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,164 @@ | ||
# frozen_string_literal: true | ||
|
||
describe RuboCop::Cop::RSpec::Stubject do | ||
subject(:cop) { described_class.new } | ||
|
||
it 'complains when subject is stubbed' do | ||
expect_violation(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
before do | ||
allow(foo).to receive(:bar).and_return(baz) | ||
^^^^^^^^^^ Do not stub your test subject. | ||
end | ||
it 'uses expect twice' do | ||
expect(foo.bar).to eq(baz) | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'complains when subject is mocked' do | ||
expect_violation(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
before do | ||
expect(foo).to receive(:bar).and_return(baz) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not stub your test subject. | ||
expect(foo).to receive(:bar) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not stub your test subject. | ||
expect(foo).to receive(:bar).with(1) | ||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not stub your test subject. | ||
end | ||
it 'uses expect twice' do | ||
expect(foo.bar).to eq(baz) | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'ignores stub within context where subject name changed' do | ||
expect_no_violations(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
context 'when I shake things up' do | ||
subject(:bar) { described_class.new } | ||
it 'tries to trick rubocop-rspec' do | ||
allow(foo).to receive(:baz) | ||
end | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags nested stubjects when nested subject uses same name' do | ||
expect_violation(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
context 'when I shake things up' do | ||
subject(:foo) { described_class.new } | ||
before do | ||
allow(foo).to receive(:wow) | ||
^^^^^^^^^^ Do not stub your test subject. | ||
end | ||
it 'tries to trick rubocop-rspec' do | ||
expect(foo).to eql(:neat) | ||
end | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'ignores nested stubs when nested subject is anonymous' do | ||
expect_no_violations(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
context 'when I shake things up' do | ||
subject { described_class.new } | ||
before do | ||
allow(foo).to receive(:wow) | ||
end | ||
it 'tries to trick rubocop-rspec' do | ||
expect(foo).to eql(:neat) | ||
end | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags nested stubjects when example group does not define subject' do | ||
expect_violation(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
context 'when I shake things up' do | ||
before do | ||
allow(foo).to receive(:wow) | ||
^^^^^^^^^^ Do not stub your test subject. | ||
end | ||
it 'tries to trick rubocop-rspec' do | ||
expect(foo).to eql(:neat) | ||
end | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags nested stubjects' do | ||
expect_violation(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
context 'when I shake things up' do | ||
subject(:bar) { described_class.new } | ||
before do | ||
allow(foo).to receive(:wow) | ||
allow(bar).to receive(:wow) | ||
^^^^^^^^^^ Do not stub your test subject. | ||
end | ||
it 'tries to trick rubocop-rspec' do | ||
expect(bar).to eql(foo) | ||
end | ||
end | ||
end | ||
RUBY | ||
end | ||
|
||
it 'flags deeply nested stubjects' do | ||
expect_violation(<<-RUBY) | ||
describe Foo do | ||
subject(:foo) { described_class.new } | ||
context 'level 1' do | ||
subject(:bar) { described_class.new } | ||
context 'level 2' do | ||
subject(:baz) { described_class.new } | ||
before do | ||
allow(foo).to receive(:wow) | ||
allow(bar).to receive(:wow) | ||
allow(baz).to receive(:wow) | ||
^^^^^^^^^^ Do not stub your test subject. | ||
end | ||
end | ||
end | ||
end | ||
RUBY | ||
end | ||
end |