user3809888
user3809888

Reputation: 407

RSpec: How to mock an object and methods that take parameters

I'm writing RSpec unit tests for a CommandLineInterface class that I've created for my Directory object. The CommandLineInterface class uses this Directory object to print out a list of people in my Directory. Directory has a #sort_by(param) method that returns an array of strings. The order of the strings depends on the param passed to the #sort_by method (e.g., sort_by("gender"). What would be the correct way to mock out this Directory behavior in my CLI specs? Would I use an instance_double? I am not sure how to do this for a method that takes parameters, like sorting by gender.

I'm only using Ruby and RSpec. No Rails, ActiveRecord, etc. being used here.

Snippets from the class and method I want to mock out:

class Directory
  def initialize(params)
    #
  end

  def sort_by(param)
    case param
    when "gender" then @people.sort_by(&:gender)
    when "name" then @people.sort_by(&:name)
    else raise ArgumentError
    end
  end
end

Upvotes: 1

Views: 4406

Answers (1)

Jesper
Jesper

Reputation: 4555

It all depends on how your objects are collaborating.

Some information is lacking in your question:

  • How does CommandLineInterface use Directory? Does it create an instance by itself or does it receive one as an argument?
  • Are you testing class methods or instance methods? (Prefer instance methods)

Here's how you could do it if you pass in the dependent object:

require 'rspec/autorun'

class A
  def initialize(b)
    @b = b
  end

  def foo(thing)
    @b.bar(thing)
  end
end

RSpec.describe A do
  describe '#foo' do
    context 'when given qux' do
      let(:b) { double('an instance of B') }
      let(:a) { A.new(b) }
      it 'calls b.bar with qux' do
        expect(b).to receive(:bar).with('qux')
        a.foo('qux')
      end
    end
  end
end

If the class initializes the dependant object and it isn't important to know which instance got the message you can do this:

require 'rspec/autorun'

B = Class.new

class A
  def initialize
    @b = B.new
  end

  def foo(thing)
    @b.bar(thing)
  end
end

RSpec.describe A do
  describe '#foo' do
    context 'when given qux' do
      let(:a) { A.new }
      it 'calls b.bar with qux' do
        expect_any_instance_of(B).to receive(:bar).with('qux')
        a.foo('qux')
      end
    end
  end
end

If you just want to stub out the return value and not test whether the exact message was received, you can use allow:

require 'rspec/autorun'

B = Class.new

class A
  def initialize
    @b = B.new
  end

  def foo(thing)
    thing + @b.bar(thing)
  end
end

RSpec.describe A do
  describe '#foo' do
    context 'when given qux' do
      let(:a) { A.new }
      it 'returns qux and b.bar' do
        allow_any_instance_of(B).to receive(:bar).with('qux') { 'jabber' }
        expect(a.foo('qux')).to eq('quxjabber')
      end
    end
  end
end

Upvotes: 2

Related Questions