lib/rubocop/cop/rspec/subject_stub.rb in rubocop-rspec-2.7.0 vs lib/rubocop/cop/rspec/subject_stub.rb in rubocop-rspec-2.8.0

- old
+ new

@@ -5,10 +5,13 @@ module RuboCop module Cop module RSpec # Checks for stubbed test subjects. # + # Checks nested subject stubs for innermost subject definition + # when subject is also defined in parent example groups. + # # @see https://robots.thoughtbot.com/don-t-stub-the-system-under-test # @see https://samphippen.com/introducing-rspec-smells-and-where-to-find-them#smell-1-stubject # @see https://github.com/rubocop-hq/rspec-style-guide#dont-stub-subject # # @example @@ -20,10 +23,24 @@ # allow(article).to receive(:author).and_return(nil) # expect(article.description).to include('by an unknown author') # end # end # + # # bad + # describe Article do + # subject(:foo) { Article.new } + # + # context 'nested subject' do + # subject(:article) { Article.new } + # + # it 'indicates that the author is unknown' do + # allow(article).to receive(:author).and_return(nil) + # expect(article.description).to include('by an unknown author') + # end + # end + # end + # # # good # describe Article do # subject(:article) { Article.new(author: nil) } # # it 'indicates that the author is unknown' do @@ -34,33 +51,41 @@ class SubjectStub < Base include TopLevelGroup MSG = 'Do not stub methods of the object under test.' - # @!method subject(node) + # @!method subject?(node) # Find a named or unnamed subject definition # # @example anonymous subject - # subject(parse('subject { foo }').ast) do |name| + # subject?(parse('subject { foo }').ast) do |name| # name # => :subject # end # # @example named subject - # subject(parse('subject(:thing) { foo }').ast) do |name| + # subject?(parse('subject(:thing) { foo }').ast) do |name| # name # => :thing # end # # @param node [RuboCop::AST::Node] # # @yield [Symbol] subject name - def_node_matcher :subject, <<-PATTERN - (block - (send nil? - {:subject (sym $_) | $:subject} - ) args ...) + def_node_matcher :subject?, <<-PATTERN + (block + (send nil? + {:subject (sym $_) | $:subject} + ) args ...) PATTERN + # @!method let?(node) + # Find a memoized helper + def_node_matcher :let?, <<-PATTERN + (block + (send nil? :let (sym $_) + ) args ...) + PATTERN + # @!method message_expectation?(node, method_name) # Match `allow` and `expect(...).to receive` # # @example source that matches # allow(foo).to receive(:bar) @@ -71,11 +96,11 @@ # expect(foo).to receive(:bar).with(1).and_return(2) # def_node_matcher :message_expectation?, <<-PATTERN (send { - (send nil? { :expect :allow } (send nil? {% :subject})) + (send nil? { :expect :allow } (send nil? %)) (send nil? :is_expected) } #Runners.all #message_expectation_matcher? ) @@ -87,41 +112,42 @@ :receive :receive_messages :receive_message_chain :have_received } ...) PATTERN def on_top_level_group(node) - @explicit_subjects = find_all_explicit_subjects(node) + @explicit_subjects = find_all_explicit(node, &method(:subject?)) + @subject_overrides = find_all_explicit(node, &method(:let?)) find_subject_expectations(node) do |stub| add_offense(stub) end end private - def find_all_explicit_subjects(node) + def find_all_explicit(node) node.each_descendant(:block).with_object({}) do |child, h| - name = subject(child) + name = yield(child) next unless name - outer_example_group = child.each_ancestor.find do |a| + outer_example_group = child.each_ancestor(:block).find do |a| example_group?(a) end h[outer_example_group] ||= [] h[outer_example_group] << name end end def find_subject_expectations(node, subject_names = [], &block) - subject_names = @explicit_subjects[node] if @explicit_subjects[node] + subject_names = [*subject_names, *@explicit_subjects[node]] + subject_names -= @subject_overrides[node] if @subject_overrides[node] - expectation_detected = (subject_names + [:subject]).any? do |name| - message_expectation?(node, name) - end + names = Set[*subject_names, :subject] + expectation_detected = message_expectation?(node, names) return yield(node) if expectation_detected - node.each_child_node do |child| + node.each_child_node(:send, :def, :block, :begin) do |child| find_subject_expectations(child, subject_names, &block) end end end end