Ruby: Rspec double confusion
You're close. Here's how to mock that call to Person.new:
First, let's make the Person
and Product
classes a bit more realistic...
class Person
def default_number_of_products
Product.new.default_number
end
end
class Product
attr_reader :default_number
def initialize
@default_number = 3
end
end
Now for the Person
tests that mock the Product
class...
describe Person do
subject(:person) { described_class.new }
describe '#default_number_of_products' do
let(:product) { instance_double(Product, default_number: 42) }
before do
allow(Product).to receive(:new).and_return(product)
end
it 'returns 42' do
expect(person.default_number_of_products).to eq(42)
end
end
end
Had the code in Product
executed, the call to person.default_number_of_products
would have returned 3
. Instead, this test spied on Product.new
and returned a double in place of a real Product
. So, when the code in person.default_number_of_products
executes, it sees the double, which has a default_number
of 42.
Finally, in your question above you mentioned that you thought that mocks should allow you to create one class without have to create the collaborators. That is a true statement. However, in the tests above, instance_double(Product, ...)
actually creates a duck type from the real Product
class. So, it would need to be defined. If you wanted to TDD this before creating the Product
class, you could pass a string in place of the class name, like this:
let(:product) { instance_double('product', default_number: 42) }
HTH
What is the argument passed into RSpec's double method used for?
It sets a title for the double to be used in error messages.
#<Double "A Double"> received unexpected message :foo with (no args)
If you have multiple doubles, it will help you to figure out which one received an unexpected message.
In addition to the title, you can make your double respond in a predefined way to a method without using something like allow(double_x).to receive(:foo).and_return('bar')
to set it up:
let(:double_x) { double("Double X", foo: 'bar') }
# double_x.foo
# => "bar"
The title is optional and can be left out.
RSpec - How to properly use doubles and stub methods on helper objects?
I usually do something like this.
let(:fake_service) { your double here or whatever }
before do
allow(SomeService).to receive(:new).and_return(fake_service)
# this might not be needed, depending on how you defined your `fake_service`
allow(fake_service).to receive(:process).and_return(fake_results)
end
Where's the official doc for RSpec's instance_double method that outline how you can configure the method's return value?
From the Yard documentation:
instance_double(doubled_class, stubs)
Parameters:
- doubled_class (String, Class)
- stubs (Hash) — hash of message/return-value pairs
is_happy: true
will make the double return true
when sent the the message is_happy
.
RSpec has both the normal API documentation which is generated by Yard from the code and the guide style documenation that you have linked. Its quite common to have both in a well documented project since they serve different purposes.
Is there a way in RSpec to return the double object itself on doubles?
You can configure the messages in bulk via receive_messages
:
relation = double(:relation, each_record: [record])
allow(relation).to receive_messages(in_batches: relation, active: relation)
There's also as_null_object
which returns the double in response to arbitrary messages:
relation = double(:relation, each_record: [record]).as_null_object
relation.in_batches.active.each_record #=> [record]
relation.foo.bar.baz.each_record #=> [record]
How can I double variable within method in rspec rails
The problem with your test is that it doesn't test anything. The controller test should make a request to test the method get :foo!
About stubbing, in your case current_user
method can be stubbed instead:
RSpec.describe TestController, type: :controller do
describe "#foo" do
it "should be passed" do
@specific_user = FactoryGirl.create(:user)
allow(controller).to receive(:foo!).and_return(true)
allow(controller).to receive(:current_user).and_return(@specific_user)
end
end
end
And yeah, in the controller test the controller instance can be accessed by calling controller
method.
What also allows to set an instance variable in this controller:
RSpec.describe TestController, type: :controller do
describe "#foo" do
it "should be passed" do
@specific_user = FactoryGirl.create(:user)
allow(controller).to receive(:foo!).and_return(true)
controller.instance_variable_set(:@user, @specific_user)
end
end
end
Related Topics
How to Reinstall a Gem Using Bundler
Using Ruby's Optionparser to Parse Sub-Commands
Rails: Ensure Only One Boolean Field Is Set to True at a Time
How to Render a Partial in Sinatra View (Haml in Haml)
Check Ruby Http Response for Success
Error Message "Xcode Alone Is Not Sufficient on Sierra"
How to Call Rake Tasks That Are Defined in the Standard Rakefile from an Other Ruby Script
Get Today's Date in Jekyll with Liquid Markup
Namespacing Thor Commands in a Standalone Ruby Executable
Deleting Table from Schema - Rails
How to Put Assertions in Ruby Code