144

When writing RSpec tests, I find myself writing a lot of code that looks like this in order to ensure that a method was called during the execution of a test (for the sake of argument, let's just say I can't really interrogate the state of the object after the call because the operation the method performs is not easy to see the effect of).

describe "#foo"
  it "should call 'bar' with appropriate arguments" do
    called_bar = false
    subject.stub(:bar).with("an argument I want") { called_bar = true }
    subject.foo
    expect(called_bar).to be_true
  end
end

What I want to know is: Is there a nicer syntax available than this? Am I missing some funky RSpec awesomeness that would reduce the above code down to a few lines? should_receive sounds like it should do this but reading further it sounds like that's not exactly what it does.

Flip
  • 6,233
  • 7
  • 46
  • 75
Mikey Hogarth
  • 4,672
  • 7
  • 28
  • 44
  • 3
    Check here: http://stackoverflow.com/questions/1328277/how-to-say-should-receive-more-times-in-rspec – kddeisz Jan 21 '14 at 15:32
  • @Peter Alfvin The OP was asking for syntax on `should_receive`, so I thought that question would help. – kddeisz Jan 21 '14 at 18:43

4 Answers4

181
it "should call 'bar' with appropriate arguments" do
  expect(subject).to receive(:bar).with("an argument I want")
  subject.foo
end
wacko
  • 3,384
  • 1
  • 15
  • 22
  • 1
    Sorry, I'm not understanding how this format of "to .. receive(:bar)" checks for the value of "called_bar" in this example. Can you explain that to me? – ecoding5 Jun 29 '15 at 03:51
  • 2
    @ecoding5 no. It doesn't and neither should check for `called_bar`. That was just a flag to ensure that the method was called, but with `expect(...).to receive(...)` you are already covering that. It's more clear and semantic – wacko Jun 29 '15 at 19:52
  • @wacko oooh, got it, thanks for the clearing that up. I didn't catch it the first time. – ecoding5 Jun 30 '15 at 03:52
112

In the new rspec expect syntax this would be:

expect(subject).to receive(:bar).with("an argument I want")
Dave Powers
  • 2,051
  • 2
  • 30
  • 34
Uri Agassi
  • 36,848
  • 14
  • 76
  • 93
45

The below should work

describe "#foo"
  it "should call 'bar' with appropriate arguments" do
     subject.stub(:bar)
     subject.foo
     expect(subject).to have_received(:bar).with("Invalid number of arguments")
  end
end

Documentation: https://github.com/rspec/rspec-mocks#expecting-arguments

bjhaid
  • 9,592
  • 2
  • 37
  • 47
  • Thank you - I was receiving "NoMethodError" has_received? - think this might be to do with rspec versoins though. I found another solution that worked for me (the one marked correct above) – Mikey Hogarth Jan 21 '14 at 16:34
  • 1
    @MikeyHogarth This answer was suggesting `have_received` (the after the fact "spies" approach), not `has_received`, which is not part of any version of RSpec that I know. – Peter Alfvin Jan 21 '14 at 16:44
4

To fully comply with RSpec ~> 3.1 syntax and rubocop-rspec's default option for rule RSpec/MessageSpies, here's what you can do with spy:

Message expectations put an example's expectation at the start, before you've invoked the code-under-test. Many developers prefer using an arrange-act-assert (or given-when-then) pattern for structuring tests. Spies are an alternate type of test double that support this pattern by allowing you to expect that a message has been received after the fact, using have_received.

# arrange.
invitation = spy('invitation')

# act.
invitation.deliver("foo@example.com")

# assert.
expect(invitation).to have_received(:deliver).with("foo@example.com")

If you don't use rubocop-rspec or using non-default option. You may, of course, use RSpec 3 default with expect.

dbl = double("Some Collaborator")
expect(dbl).to receive(:foo).with("foo@example.com")
Yi Zeng
  • 32,020
  • 13
  • 97
  • 125