RSpec instance_double respond_to? strange behavior - ruby

I've got
let(:ssl_socket) { instance_double(OpenSSL::SSL::SSLSocket) }
Now I want to test if the object I have is an instance of OpenSSL::SSL::SSLSocket. I don't want to use be_an_instance_of matcher because I want my test to check more like "duck typing" way. So I want to know if object responds to 'ssl_version' method
it 'should change socket to ssl' do
#do some stuff and set socket = ssl_socket
expect(socket).to respond_to(:ssl_version)
end
It doesn't work
expected #<InstanceDouble(OpenSSL::SSL::SSLSocket) (anonymous)> to respond to :ssl_version
But if I do it using other matcher
it 'should change socket to ssl' do
#do some stuff and set socket = ssl_socket
expect(socket).to receive(:ssl_version)
socket.ssl_version
end
then InstanceDouble will actually check if :ssl_version method is present in OpenSSL::SSL::SSLSocket instances. Is this a feature or a bug of RespondTo matcher? Should I use Receive matcher?

instance_double basically creates a Struct instance under the hood.
Out of the box it’s an object, that knows nothing about wrapped class. receive extends stubs attribute of it.
So yes, one should use receive in this case. To be more precise, one should not test RSpec itself and what you are doing is testing instance_double implementation.

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)

Minitest stub passing block to mock instance

I stumbled across a curious behaviour and haven't been able to figure out what I was doing wrong. I hope somebody can enlighten me.
I was trying to stub the Redis client during my tests in a Rails application. Therefore I was using the MockRedis gem. I have created a RedisFactory class with the single class method .create, which I wanted to stub with an optional MockRedis instance like so:
def stub_redis(mock_redis = MockRedis.new)
RedisFactory.stub :create, mock_redis { yield }
end
This did not work and always threw a ArgumentError in Foo#Bar wrong number of arguments (0 for 1). Some further debugging revealed that a call of RedisFactory.create 'foo' within the stub-block resulted in an error that 'foo' is no method on instance of MockRedis::Database.
However, I have been able to solve this problem with the following code snippet, using a lambda function to catch the incoming arguments:
def stub_redis(mock_redis = MockRedis.new)
RedisFactory.stub(:create, ->(*_args) { mock_redis }) { yield }
end
Could anybody explain this behaviour?
As of now MiniTest tries to guess if the passed val_or_callable is a Proc by checking whether it responds to call, cf.:
https://apidock.com/ruby/Proc/call
https://github.com/seattlerb/minitest/blob/b84b8176930bacb4d70d6bef476b1ea0f7c94977/lib/minitest/mock.rb#L226
Unfortunately, in this specific case Redis as well as the passed MockRedis-instance both provide a generic call-method for executing Redis commands, cf.:
https://github.com/brigade/mock_redis/blob/master/lib/mock_redis.rb#L51
You already found the correct workaround. In this case, your only chance is to explicitly use the proc-version of stub.
Note: There are some communities using def call as a pattern with ServiceObjects in Ruby which may have a difficult time using minitest's stub. It is probably a good idea to open an issue in seattlerb/minitest.

What is double method in rspec for?

It is stated in rspec doc that I should use double method in order to create test double.
But I can see that it works perfectly ok even if I don't use double. Is there anything wrong with not using double? Also if I'm not using double how MyClass gets stub and other rspec methods? Are they available for all objects when running in rspec?
require 'spec_helper'
class MyClass
def self.run
new.execute
end
def execute
'foo'
end
end
describe MyClass do
it 'should stub instance method' do
obj = MyClass.new
obj.stub(:execute).and_return('bar')
obj.execute.should == 'bar'
end
it 'should stub class method' do
MyClass.stub(:run).and_return('baz')
MyClass.run.should == 'baz'
end
end
Edit: I just reread your question and realized I didn't quite answer it. Leaving my original answer because it's related, but here's your specific answer:
The reason you don't need a double is because you're stubbing class methods, rather than instance methods. double is only useful for dealing with instances of the class, not the class itself.
Old answer that explains double some more:
You should always use real classes instead of test doubles when you can. This will exercise more of your code and make your tests more comprehensive. Test doubles are used in situations where you can't or shouldn't use a real object. For example, if a class can't be instantiated without hitting an external resource (like a network or a database), or has a large number of dependencies, and you're just testing something that uses it, you might want to create a double and stub some methods on the double.
Here's a more specific example: let's say you are testing MyClass, but in order to instantiate MyClass, you need to pass in a FooLogger:
mylogger = FooLogger.new
myclass = MyClass.new logger: mylogger
If FooLogger.new opens a syslog socket and starts spamming it right away, every time you run your tests, you'll be logging. If you don't want to spam your logs during this test, you can instead create a double for FooLogger and stub out a method on it:
mylogger = double(FooLogger)
mylogger.stub(:log)
myclass = MyClass.new logger: mylogger
Because most well-designed classes can be instantiated without any side-effects, you can usually just use the real object instead of a double, and stub methods on that instead. There are other scenarios where classes have many dependencies that make them difficult to instantiate, and doubles are a way to get past the cruft and test the thing you really care about.
In my experience, needing to use a double is a code smell, but we often have to use classes that we can't easily change (e.g. from a gem), so it's a tool you might need from time to time.
With RSpec Mocks 3.0 the behaviour of doubles has changed. You now may verify doubles, which means "RSpec will check that the methods
being stubbed are actually present on the underlying object if it is available", but "no checking will happen if the underlying object or class is not defined".
Verifying doubles requests you to be specific about the double type (instance, class, object, dynamic class, partial). Here is an example from the RSpec Relish for an instance double:
RSpec.describe User, '#suspend!' do
it 'notifies the console' do
notifier = instance_double("ConsoleNotifier")
expect(notifier).to receive(:notify).with("suspended as")
user = User.new(notifier)
user.suspend!
end
end

Ruby Testing: Testing for the Absence of a Method

In rspec and similar testing frameworks, how does one test for the absence of a method?
I've just started fiddling with rspec and bacon (a simplified version of rspec.) I wanted to define test that would confirm that a class only allows read access to an instance variable. So I want a class that looks like:
class Alpha
attr_reader :readOnly
#... some methods
end
I am rather stumped:
it "will provide read-only access to the readOnly variable" do
# now what???
end
I don't see how the various types of provided test can test for the absence of the accessor method. I'm a noob in ruby and ruby testing so I'm probably missing something simple.
In Ruby, you can check if an object responds to a method with obj.respond_to?(:method_name), so with rspec, you can use:
Alpha.new.should_not respond_to(:readOnly=)
Alternatively, since classes could override the respond_to? method, you can be stricter and make sure that there is no assignment method by actually calling it and asserting that it raises:
expect { Alpha.new.readOnly = 'foo' }.to raise_error(NoMethodError)
See RSpec Expectations for reference.
I believe you're looking for respond_to?, as in some_object.respond_to? :some_method.

Spec RSpec model attribute setter

I'm using Sinatra (1.2) and RSpec (2.5) and would like to create a new object with an attribute TDD style. This is how the end result should look like:
class User
def initialize(name)
#name = name
end
end
I know I have to write the example before the implementation but I'm trying to explain my question here. :) Here is the not working spec I have so far:
describe User
it "creates a new user object" do
name = mock("A name")
user = mock(User) # shouldn't do this, see the reply's
user.should_receive(:name=).with(name)
User.new(name)
end
end
When I run RSpec I get the "expected: 1 time, received 0 times" error. Any idea how I can explain RSpec I would like to assign the name attribute?
Note: I'm not using Rails, not using ActiveRecord or anything, just Ruby.
First of all, let me explain why the spec you have written doesn't work:
You set an expectation that the mock object returned by mock(User) should receive name=. There are two problems with this. First of all the mock will receive nothing, because it is never called. mock(User) returns a mock object, and it cannot be used to set expectations for what the User class object will receive (to do that simply do User.should_receive(...)). Secondly, even if you had set the expectation on the User class object, that object will never receive name=. There are two reasons for this, too: firstly because name= (had it existed) would be an instance method, and as such not called on the class object, and secondly, you declare no name= instance method. What your code does is that it sets an instance variable.
Now, how should you write a test for this? You shouldn't. Tests are to define and assert the behaviour, not the implementation. Setting an instance variable is pure implementation. There is in your example code no way to get the value of the #name instance variable from outside of the class, therefore there is no reason to write a test for it.
Obviously your code is just an example, anything useful would do something with the #name variable, and that is what you should test. Start by writing a test for what a User object will be used for, then write all the implementation needed to fulfill that test (but no more). Write a test that reflects how the object will be used in actual production code.
I'd really recommend you don't approach this using mocks. It's not what they're for. In fact, specifying getters/setters like this is not really what TDD is for. The idea is to let a requirement drive the setters/getters into existence. For example, there might be a requirement that the user's name appear in a welcome message when he/she logs in. Then you might do something like this:
describe 'login process' do
it "displays user's name after successful login" do
user = User.new("Cimm", "cimm#somewhere.com", "secret")
post "/login", :email => "cimm#somewhere.com", :password => "secret"
last_response.body.should =~ /Welcome Cimm/m
end
end
This specifies behavior, and forces you to implement a means of setting the name attribute (via the constructor, in this case) and a means of accessing it. No need to specify the constructor directly.
Do you really want to mock the very object you are developing?
require 'rspec'
class User
attr_accessor :name
def initialize(name)
#name = name
end
end
describe User do
subject {User.new "other name"}
it "creates a new user object" do
subject.should respond_to :name=
end
end

Resources