Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I mock an instance variable in rspec

Tags:

rspec

I have two class OneClass and AnotherClass:

class OneClass
  def initialize(*args)
    @another_member = AnotherClass.new()
  end

  def my_method()
    if @another_member.another_method1() then
      @another_member.another_method2()
    end
    @another_member.another_method3()
  end
end

I am getting to write unit for OneClass. How can I mock @another_member?

like image 550
Mike Avatar asked May 16 '16 06:05

Mike


People also ask

What is mocking in RSpec?

Mocking is a technique in test-driven development (TDD) that involves using fake dependent objects or methods in order to write a test. There are a couple of reasons why you may decide to use mock objects: As a replacement for objects that don't exist yet.

What is double in RSpec?

A Double is an object which can “stand in” for another object. You're probably wondering what that means exactly and why you'd need one. This is a simple class, it has one method list_student_names, which returns a comma delimited string of student names.

What is let in Ruby?

Use let to define a memoized helper method. The value will be cached across multiple calls in the same example but not across examples. Note that let is lazy-evaluated: it is not evaluated until the first time the method it defines is invoked.


2 Answers

With the idea of Anthony, I make it work.

describe OneClass do
  before(:each) { @one_object = OneClass.new }

  describe 'my_method' do
    it 'should work' do
      mock_member = double
      allow(mock_member).to receive(:another_method1).and_return(true)
      @one_object.instance_variable_set(:@another_member, mock_member)

      @one_object.my_method()

      expect(mock_member).to have_received(:another_method1)
    end
  end
end
like image 176
Mike Avatar answered Oct 18 '22 19:10

Mike


You can't mock an instance variable. You can only mock methods. One option is to define a method inside OneClass that wraps the another_member, and mock that method.

class OneClass
  def initialize(*args)
  end

  def my_method()
    if another_member.another_method1() then
      another_member.another_method2()
    end
    another_member.another_method3()
  end

  private

  def another_member
    @another_member ||= AnotherClass.new()
  end

end

However, you don't have to, there is a better way to write and test your code. In this case a better approach to mocking is to use a pattern called Dependency Injection.

You pass your dependency to the initializer.

class OneClass
  def initialize(another: AnotherClass, whatever:, somethingelse:)
    @another_member = another.new()
  end

  def my_method()
    if @another_member.another_method1() then
      @another_member.another_method2()
    end
    @another_member.another_method3()
  end
end

(Note I used keyword arguments, but you don't have to. You can also use the standard args approach).

Then, in the test suite you simply provide the test object.

let(:test_another) {
  Class.new do
    def another_method1
      :foo
    end
    def another_method2
      :bar
    end
    def another_method3
      :baz
    end
  end
}

it "does something" do
  subject = OneClass.new(another: test_another)
  # ...
end

There are several advantages of this approach. In particular, you avoid using mock in the tests and you really test the object in isolation.

like image 24
Simone Carletti Avatar answered Oct 18 '22 17:10

Simone Carletti