What is double method in rspec for?

33,534

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.

Share:
33,534
grafthez
Author by

grafthez

Updated on June 09, 2020

Comments

  • grafthez
    grafthez almost 4 years

    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
    
  • grafthez
    grafthez about 11 years
    Actually when you look at my example, first spec stubs instance method and the second one stubs class method. It looks like both work ok with no double used before. That's why I wonder what is an extra magic double gives me.
  • grafthez
    grafthez about 11 years
    Take a look at this example I created gist.github.com/anonymous/5101448. If I want to test SchedulerJob I need to stub RequestSchedule and mock RequestToQueuePusher right? Also what bothers me is that SchedulerJob is tightly coupled with two remaining classes. As I come from Java world, I'd normally extract them as dependencies as there is no easy way to fake objects which are created the hard-coded way. In Ruby it seems not to be an issue. I see a lot of object like my SchedulerJob. I know there is a way to fake them easily, but for me it violates some SOLID principles
  • Jim Stewart
    Jim Stewart about 11 years
    That's poor design in the Ruby world too. There should be a way to inject the RequestSchedule dependency to make testing easier. You could do something like this: fakeschedule = double(RequestSchedule); RequestSchedule.stub(:new).and_return(fakeschedule);. It certainly should be refactored.
  • grafthez
    grafthez about 11 years
    Exactly, this is my thought too. But I don't know why I can't see injection used in Ruby world. People prefer to new objects inside methods and use things like rspec stub you mentioned to provide fake instances. The point is injection is not only for testing, it makes code better in general. Don't know why this technique is not common in Ruby world. And for me, java coder, it's hard to make a switch.
  • Luke Griffiths
    Luke Griffiths almost 9 years
    So to really boil it down, a double is not meant to be used on the main character, but rather on one of the supporting roles, so the code of the test can focus on the main character.