rubocop / rubocop-rspec

Code style checking for RSpec files.

Home Page:https://docs.rubocop.org/rubocop-rspec

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Is RSpec/ReceiveNever even a valid cop?

ekohl opened this issue · comments

Summary

The RSpec/ReceiveNever cop says:

Prefer not_to receive(...) over receive(...).never

When running rspec it says:

allow(...).not_to receive is not supported since it doesn't really make sense. What would it even mean?

Either I'm doing something wrong, or the whole cop is invalid.

Details

In #628 the RSpec/ReceiveNever cop was added, but there's this code:
https://github.com/puppetlabs/puppetlabs-stdlib/blob/7e7ded41443750e6643599fa02ad4456e781fede/spec/functions/loadjson_spec.rb#L31-L35

Or a minimal example: allow(JSON).to receive(:parse).never. The cop suggests this should be allow(JSON).not_to receive(:parse) but when I run that, the tests fail with:

$ bundle exec rspec ./spec/functions/loadjson_spec.rb
...
Failures:

  1) loadjson when calling with valid arguments when a non-existing file is specified 
     Failure/Error: allow(JSON).not_to receive(:parse)
       `allow(...).not_to receive` is not supported since it doesn't really make sense. What would it even mean?
     # ./spec/functions/loadjson_spec.rb:34:in `block (4 levels) in <top (required)>'
     # /home/ekohl/.local/share/gem/ruby/bin/rspec:25:in `load'
     # /home/ekohl/.local/share/gem/ruby/bin/rspec:25:in `<top (required)>'
     # /usr/bin/bundle:25:in `load'
     # /usr/bin/bundle:25:in `<main>'

Interesting. Does

allow(JSON).to receive(:parse).never

work as you expect it to?
Would calling a JSON.parse later in the example result in a failure?

I’d say:

  • we need to ignore allow, and only flag expect
  • we should extend RSpec to warn of allow.to receive.never just like it does for negatiion

Would you like to send such PRs?

Interesting. Does

allow(JSON).to receive(:parse).never

work as you expect it to?

I think this covers all cases:

describe 'rubocop-rspec 1755' do
  describe 'allow.to receive.never' do
    before { allow(Integer).to receive(:sqrt).never }

    it 'passes' do
      # Nothing here
    end

    it 'fails' do
      Integer.sqrt(9)
    end
  end

  describe 'expect.to receive.never' do
    before { expect(Integer).to receive(:sqrt).never }

    it 'passes' do
      # Nothing here
    end

    it 'fails' do
      Integer.sqrt(9)
    end
  end

  describe 'allow.not_to receive' do
    before { allow(Integer).not_to receive(:sqrt) }

    # This never passes since rspec doesn't allow it

    it 'fails' do
      Integer.sqrt(9)
    end
  end

  describe 'expect.not_to receive' do
    before { expect(Integer).not_to receive(:sqrt) }

    it 'passes' do
      # Nothing here
    end

    it 'fails' do
      Integer.sqrt(9)
    end
  end
end

And when running it:

$ bundle exec rspec
.F.FF.F

Failures:

  1) rubocop-rspec 1755 allow.to receive.never fails
     Failure/Error: Integer.sqrt(9)
     
       (Integer (class)).sqrt(9)
           expected: 0 times with any arguments
           received: 1 time with arguments: (9)
     # ./spec/allow_never_spec.rb:10:in `block (3 levels) in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:45:in `block in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:33:in `<top (required)>'

  2) rubocop-rspec 1755 expect.to receive.never fails
     Failure/Error: Integer.sqrt(9)
     
       (Integer (class)).sqrt(9)
           expected: 0 times with any arguments
           received: 1 time with arguments: (9)
     # ./spec/allow_never_spec.rb:22:in `block (3 levels) in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:45:in `block in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:33:in `<top (required)>'

  3) rubocop-rspec 1755 allow.not_to receive fails
     Failure/Error: before { allow(Integer).not_to receive(:sqrt) }
       `allow(...).not_to receive` is not supported since it doesn't really make sense. What would it even mean?
     # ./spec/allow_never_spec.rb:27:in `block (3 levels) in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:45:in `block in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:33:in `<top (required)>'

  4) rubocop-rspec 1755 expect.not_to receive fails
     Failure/Error: Integer.sqrt(9)
     
       (Integer (class)).sqrt(9)
           expected: 0 times with any arguments
           received: 1 time with arguments: (9)
     # ./spec/allow_never_spec.rb:44:in `block (3 levels) in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:45:in `block in <top (required)>'
     # /usr/share/gems/gems/bundler-2.4.10/libexec/bundle:33:in `<top (required)>'

Finished in 0.01234 seconds (files took 0.08014 seconds to load)
7 examples, 4 failures

Failed examples:

rspec ./spec/allow_never_spec.rb:9 # rubocop-rspec 1755 allow.to receive.never fails
rspec ./spec/allow_never_spec.rb:21 # rubocop-rspec 1755 expect.to receive.never fails
rspec ./spec/allow_never_spec.rb:31 # rubocop-rspec 1755 allow.not_to receive fails
rspec ./spec/allow_never_spec.rb:43 # rubocop-rspec 1755 expect.not_to receive fails

So they all appear to be the same, except for the one "What would it even mean?".

we need to ignore allow, and only flag expect

Given how rspec behaves, I'd say that makes sense now.

we should extend RSpec to warn of allow.to receive.never just like it does for negatiion

Agreed. This always confused me. Especially combined with the RSpec/MessageSpies in its default have_received mode which enforces allow + have_received over expect.

Would you like to send such PRs?

I wouldn't know where to get started and I'm afraid I don't have that much time available to dive into it.