RSpec Mock Object Example

49,173

Solution 1

Here's an example of a simple mock I did for a controller test in a rails application:

before(:each) do
  @page = mock_model(Page)
  @page.stub!(:path)
  @page.stub!(:find_by_id)
  @page_type = mock_model(PageType)
  @page_type.stub!(:name)
  @page.stub!(:page_type).and_return(@page_type)
end

In this case, I'm mocking the Page & PageType models (Objects) as well as stubbing out a few of the methods I call.

This gives me the ability to run a tests like this:

it "should be successful" do
  Page.should_receive(:find_by_id).and_return(@page)
  get 'show', :id => 1
  response.should be_success
end

I know this answer is more rails specific, but I hope it helps you out a little.


Edit

Ok, so here is a hello world example...

Given the following script (hello.rb):

class Hello
  def say
    "hello world"
  end
end

We can create the following spec (hello_spec.rb):

require 'rubygems'
require 'spec'

require File.dirname(__FILE__) + '/hello.rb'

describe Hello do
  context "saying hello" do 
    before(:each) do
      @hello = mock(Hello)
      @hello.stub!(:say).and_return("hello world")
    end

    it "#say should return hello world" do
      @hello.should_receive(:say).and_return("hello world")
      answer = @hello.say
      answer.should match("hello world")
    end
  end
end

Solution 2

mock is deprecated based on this github pull.

Now instead we can use double - more here...

 before(:each) do
   @page = double("Page")
   end

  it "page should return hello world" do
    allow(@page).to receive(:say).and_return("hello world")
    answer = @page.say
    expect(answer).to eq("hello world")
  end

Solution 3

I don't have enough points to post a comment to an answer but I wanted to say that the accepted answer also helped me with trying to figure out how to stub in a random value.

I needed to be able to stub an object's instance value that is randomly assigned for example:

class ClumsyPlayer < Player do

  def initialize(name, health = 100)
    super(name, health)
    @health_boost = rand(1..10)
  end
end

Then in my spec I had a problem on figuring out how to stub the clumsy player's random health to test that when they get a heal, they get the proper boost to their health.

The trick was:

@player.stub!(health_boost: 5)

So that stub! was the key, I had been just using stub and was still getting random rspec passes and failures.

So thank you Brian

Solution 4

Current (3.x) RSpec provides both pure mock objects (as in tokhi's answer) and partial mocking (mocking calls to an existing object). Here's an example of partial mocking. It uses expect and receive to mock an Order's call to a CreditCardService, so that the test passes only if the call is made without having to actually make it.

class Order
  def cancel
     CreditCardService.instance.refund transaction_id
  end
end

describe Order do
  describe '#cancel' do
    it "refunds the money" do
      order = Order.new
      order.transaction_id = "transaction_id"
      expect(CreditCardService.instance).to receive(:refund).with("transaction_id")
      order.cancel
    end
  end
end

In this example the mock is on the return value of CreditCardService.instance, which is presumably a singleton.

with is optional; without it, any call to refund would satisfy the expectation. A return value could be given with and_return; in this example it is not used, so the call returns nil.


This example uses RSpec's current (expect .to receive) mocking syntax, which works with any object. The accepted answer uses the old rspec-rails mock_model method, which was specific to ActiveModel models and was moved out of rspec-rails to another gem.

Solution 5

Normally you want to use a Mock Object when you want to delegate some functionality to other object but you don't want to test the real functionality on your current test, so you replace that object with other that is easier to control. Let's call this object "dependency"...

The thing that you are testing (object/method/function...) can interact with this dependency by calling methods to...

  • Query for something.
  • Change something or produce some side effect.

When calling a method to query for something

When you are using the dependency to "query" for something, you don't need to use the "mock API" because you can just use a regular object, and test for the expected output in the object that you are testing... for example:

describe "Books catalog" do
  class FakeDB
    def initialize(books:)
      @books = books
    end

    def fetch_books
      @books
    end
  end

  it "has the stored books" do
    db = FakeDB.new(books: ["Principito"])
    catalog = BooksCatalog.new(db)
    expect(catalog.books).to eq ["Principito"]
  end
end

When calling a method to change something or produce some side effect...

When you want to make a change in your dependency or do something with side effects like inserting a new record on a database, sending an email, make a payment, etc... now instead of testing that the change or side effect was produced, you just check that you are calling the right function/method with the right attributes... for example:

describe "Books catalog" do
  class FakeDB
    def self.insert(book)
    end
  end

  def db
    FakeDB
  end

  it "stores new added books" do
    catalog = BooksCatalog.new(db)

    # This is how you can use the Mock API of rspec
    expect(db).to receive(:insert).with("Harry Potter")

    catalog.add_book("Harry Potter")
  end
end

This is a basic example, but you can do a lot just with this knowledge =)

I wrote a post with this content and a little more that maybe can be useful http://bhserna.com/2018/how-and-when-to-use-mock-objects-with-ruby-and-rspec.html

Share:
49,173
ab217
Author by

ab217

Updated on November 30, 2021

Comments

  • ab217
    ab217 over 2 years

    I am new to mock objects, and I am trying to learn how to use them in RSpec. Can someone please post an example (a hello RSpec Mock object world type example), or a link (or any other reference) on how to use the RSpec mock object API?

  • Arnaud Meuret
    Arnaud Meuret about 10 years
    You are setting up a mock object of the class you are supposed to be speccing/testing which means you are not exercising the class being devlopped at all. Pointless. Mocking is meant to help you surround the class/system under test with objects credibly posing as a real world environment albeit simpler/controlled/self-contained. It is an anti-pattern to use Mocking to check internal workings of the class being developped.
  • Dave Schweisguth
    Dave Schweisguth over 6 years
    Also, this answer is out of date. See tokhi's answer below for how to write a pure mock object and mine for how to do partial mocking in current RSpec.