RSpec::Spy
This gem allows you to write mock expectations in an AAA (Arrange-Act-Assert) fashion
with RSpec::Mocks. It does this by allowing you to declare examples with :spy => true
or :spy
(if you use config.treat_symbols_as_metadata_keys_with_true_values = true
)
so that they are effectively executed before everything else. Here's a simple example:
describe "Example" do
let(:collaborator) { stub.as_null_object }
before do
collaborator.message
end
it "should receive a message", :spy => true do
collaborator.should_have_received :message
end
it "should not receive other_message", :spy => true do
collaborator.should_not_have_received :other_message
end
end
Installation
Add this line to your application's Gemfile in the :test
group
gem 'rspec-spy'
And then execute:
$ bundle
Or install it yourself as:
$ gem install rspec-spy
Add to your spec_helper.rb in RSpec.configure
AFTER any other global before hooks, especially DatabaseCleaner:
# any config.before hooks should go above
require 'rspec-spy'
config.before(:each, :spy => true) do |example|
RSpec::Spy(example)
end
Usage
Just tag your examples with :spy => true
or :spy
(if you use
config.treat_symbols_as_metadata_keys_with_true_values = true
)
You should be able to use all of the functionality from rspec-mocks that you're
used to, including spying on class methods.
it "should receive message", :spy => true do
collaborator.should_have_received :message
end
# with config.treat_symbols_as_metadata_keys_with_true_values = true
it "should receive message", :spy do
collaborator.should_have_received :message
end
Warnings
- This is a hack, you'll want to make sure everyone on your team is aware of the behavior and these warnings.
- You should probably avoid instance vars and stick to
let
, it gets confusing because you cannot set them inbefore
blocks and use them inspy
blocks. Remember,spy
blocks actually happen beforebefore
blocks. Example (see the example at the beginning of the readme for the right way):
describe "what not to do" do
before do
@collaborator = stub.as_null_object
@collaborator.message
end
# These will fail because @collaborator is nil because this happens
# before the above before block
it "should receive a message", :spy => true do
@collaborator.should_have_received :message
end
it "should not receive other_message", :spy => true do
@collaborator.should_not_have_received :other_message
end
end
- If your tests depend on the method you are spying on returning something then you'll
need to use
and_return
in yourspy
block and if you have normal examples you'll also need tostub
it. Yes, this is annoying, but that's how rspec-mocks works and it's one of the many reasons you shoudln't mock when the return value matters (juststub
). Example:
describe "stubbing and mocking at the same time" do
let(:collaborator) { stub.as_null_object }
before do
collaborator.stub(:message) { 5 }
@result = collaborator.message
# This will fail unless you use and_return
@result.should == 5
end
it "should return 5" do
# This will fail unless you stub in before
@result.should == 5
end
it "should receive a message", :spy => true do
collaborator.should_have_received(:message).and_return(5)
end
end
Alternatives
Contributing
- Fork it
- Create your feature branch (
git checkout -b my-new-feature
) - Commit your changes (
git commit -am 'Added some feature'
) - Push to the branch (
git push origin my-new-feature
) - Create new Pull Request