RSpec mock a class with nested attributes - ruby

I'm very new to Ruby and RSpec and I'm having trouble trying to mock a chain of attributes inside of a class:
client.conversations.configuration.update(sid: user.sid)
I have already mocked the client like so:
let(:client) { class_double(Service::ClientFactory) }
However, this is what I've tried and failed using the receive method, which doesn't work.
allow(client).to receive(conversations.configuration.update).and_return("123123123")
How can I do this correctly?
Thank you!

You might want to use receive_message_chain:
allow(
client
).to receive_message_chain(:conversations, :configuration, update: "123123123")
But please have a look at the section on that page too:
Warning:
Chains can be arbitrarily long, which makes it quite painless to violate the Law of Demeter
in violent ways, so you should consider any use of receive_message_chain a code smell. [...]

Related

Rspec Double leaking to another example

I am testing a class that makes use of a client that makes external requests and I would like to mock this client, but verify that it gets called, however I am getting a double error.
My test looks like something like this:
describe '#execute' do
let(:attributes) { {foo: 'bar'} }
let(:client_double) { double('client', create: nil) }
let(:use_case) { described.class.new }
before do
allow(Client::Base).to receive(:new).and_return(client_double)
use_case.execute(attributes)
end
it 'creates something' do
expect(Something.find_by(foo: 'bar')).not_to be_nil
end
it 'calls client' do
expect(client).to have_received(:create).with('bar')
end
end
and the first example passes as expected, however rspec keeps breaking in the second example giving me this error:
#<Double "foo"> was originally created in one example but has leaked into another example and can no longer be used. rspec-mocks' doubles are designed to only last for one example, and you need to create a new one in each example you wish to use it for.
someone knows what I can do to fix it?
Reusing Fixtures with Let Methods
In this case, before is actually before(:each), which is reusing the client_double and attributes you defined with the #let helper method. The let commands make those variables functionally equivalent to instance variables within the scope of the described object, so you aren't really testing freshly-created objects in each example.
Some alternatives include:
Refactor to place all your setup into before(:each) without the let statements.
Make your tests DAMP by doing more setup within each example.
Set up new scope for a new #describe, so your test doubles/values aren't being reused.
Use your :before, :after, or :around blocks to reset state between tests, if needed.
Since you don't show the actual class or real code under test, it's hard to offer specific insights into the right way to test the object you're trying to test. It's not even clear why you feel you need to test the collaborator object within a unit test, so you might want to give some thought to that as well.
It turns out I was using a singleton as a client and haven't realized before, so it was trully class caching it through examples. To fix it all I did was mock the instantiate method instead of the new method and everything worked.
So in the end this worked:
allow(Client::Base).to receive(:instantiate).and_return(client_double)

Ruby rspec mocking a class with an attribute that is a hash

I have a class I am testing, call it myfoo. It accesses a class called yourbar. Specifically something like this...
yourbar_obj.projects[project_name]
In my spec code I have this
let(:yourbar_obj) { Class.new }
and I want to mock it to respond to the hash attribute access.
So I tried this
expect(yourbar_obj).to receive(projects).and_return(some_obj)
But when I run the code it says
NoMethodError: undefined method `projects' ...
Is it possible to mock a hash access like that? The same type of thing works for regular method calls.
I even tried adding a .with(project_name) just in case. Same error.
thoughts?
Thanks to Max's help. Here is the correct answer...
some_hash_obj[project_name] = some_obj
expect(yourbar_obj).to receive(:projects).and_return(some_hash_obj)
Two key parts. The : before projects, and some_hash_obj must be a hash. I was trying to return the value (which was an obj) at the hash index in one shot, but that ain't how it works. return the hash, and the [] will apply to it.

Rspec Class any_instance expects method_name - where it is documented?

I've stumbled upon the following piece of code in an Rspec test and I must say I more or less figured out what it does but I can't find relevant sources to prove it. Please point me to a gem or docs that describe:
describe SomeModule::Salesforce::Lead do
before do
SomeModule::Salesforce::Lead.any_instance.expects(:materialize)
end
...
end
It seems that for :each example in this spec it sets expectation on any instance of the class described above to receive a call to :materialize method AND it actually redefines the method to do nothing . The last part seems crucial because it avoids connecting to SalesForce in test environment but I can't find confirmation for this.
any_instance is documented under Working with Legacy code
You are correct in that it both sets an expectation and stubs out the original method on any given instance of a class.
Previous versions of RSpec accomplish this by monkeypatch the ruby core classes (Object and BaseObject)
RSpec 3 has a new syntax which does not rely on monkeypatching:
before do
expect_any_instance_of(SomeModule::Salesforce).to receive(:materialize)
end
Ok I've just found that I was looking in wrong sources, it doesn't come from RSpec but from Mocha Mock (expects and any_instance) http://gofreerange.com/mocha/docs/Mocha/Mock.html#expects-instance_method
Thanks #tomasz-pajor #https://stackoverflow.com/users/2928259/tomasz-pajor

How to mock properly an openssl object in rspec?

I have a method:
require 'openssl'
def extract_creds(data)
pfx = OpenSSL::PKCS12.new(data)
{ :certificate => pfx.certificate.to_pem, :key => pfx.key.to_pem }
rescue
# handle error
end
and i want to write a rspec example for it. How should i properly mock pfx object?
Why this may be the wrong question
You should generally use a mock when you want to:
Avoid testing system behavior outside the unit under test.
Avoid expensive operations, such inter-system tests.
Ensure that an object is called, but you don't care about the results.
In this case, it's not clear why you want to test this behavior, or what you expect the results to be, or why you think you might not get it. For the most part, testing a well-tested external library is not the right thing to do.
What I think the right question is
So, depending on what you really want to test, you may want to check:
Whether the method raises any exceptions.
Whether your method is actually called from some object that should call it.
In both cases, though, there seems to be no real benefit to mocking the library vs. including a real certificate as fixture data. Fixtures are better than mocks for actually exercising code.

Mocking ActiveRecord relationship beheavior in RSpec tests

I've run into this problem with testing. Let's assume I have two models, User and Post, where user has_many :posts.
I'm trying to spec out a code block that includes something like this:
user = User.find(123)
post = user.posts.find(456)
I know how to mock out the User.find and user.posts parts. The user.posts mock returns an array of Post objects. And when it get's to .find(456) part, everything breaks down with no block given exception.
So my question here is: what do I return as the result of the user.posts mock, so that .find(456) method works on it? User.first.posts.class says it's Array, but obviously there's something more that makes the AR-style find calls work. I'm not overjoyed by the prospect of mocking out find method on the returned object.
PS Before you suggest the obvious and good answer of stop mocking about and using fixtures/seeding the test database with necessary data, here's the catch: legacy scheme. Both User and Post work on top of database views not tables, and changing it so that they are tables in test database seems wrong to me.
The issue is that user.posts isn't actually a simple Array; it's an association proxy object. The way to stub it is probably something like this (though the exact syntax depends on which mocking framework you're using):
def setup
#user = mock(User)
User.stub(:find).with(123).return(#user)
user_posts = mock(Object)
#user.stub(:posts).return(user_posts)
#post = mock(Post)
user_posts.stub(:find).with(456).return(#post)
end
Then in your test, User.find(123) will return #user and #user.posts.find(456) will return #post. If you need #user.posts to act like more of the Array in your tests you can create a mock(Array) and stub the [](index) method.
You could look into the stub_chain method offered by RSpec.
http://apidock.com/rspec/Spec/Mocks/Methods/stub_chain#855-stub-chain-is-very-useful-when-testing-controller-code
Update: Per ryan2johnson9 the updated documentation is : https://relishapp.com/rspec/rspec-mocks/v/3-2/docs/working-with-legacy-code/message-chains

Resources