Benito Serna Tips and tools for Ruby on Rails developers

How and when to use mock objects with ruby and rspec

June 20, 2018

Are you new to mock objects? Are you trying to learn how to use them? Are you looking for some “hello world” examples for Mock Objects?

Mock objects can be a really good tool sometimes, but not always. This is a guide, to help you learn how and when you can use them.

In simple words what is a mock object?

A mock object is an object that you will use in place of another one.

When do you want to use a mock object?

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

So, you use a mock when the code you are testing has a dependency but you don’t want to use the real dependency, you just want to check that you are interacting with that dependency in the right way.

This interaction normally comes in three different flavors. You could want to use that dependency to query for something, to change something or to change something and expect something back.

So at least you should know how to test this interactions.

How you can use a mock object when you want to query for something?

When you are using the dependency to “query” for something, you don’t need to use the “mock API”. You can create a regular object with the expected interface, 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

In this case you don’t need to expect that a method was called

For example in this case as you are testing the behavior, of the BooksCatalog object, the important thing is the expected output in call to the books method object, and if we call other method on our dependency, our test will fail. So, why do we need another assertion?

You can design your dependencies

Give preference to what you think is better for you app over the “real” dependency. For example if you know that your “real” db library has a method get_all but you think fetch_books is better. Then go with fetch_books and write an adapter if is needed.

How you can use a mock object when you want to change something?

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. Instead of testing that the change or side effect was produced, you 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

Again… You can design your dependencies

Remember to give preference to what you think is better for you app over the “real” dependency. For example if you know that your “real” db library has a method create but you think insert is better. Then go with insert and write an adapter if is needed.

How you can use a mock object when you want to change something and expect something back?

Is better if you avoid this kind of behavior, but sometimes you will need to expect something back when asking your dependency to change something. A common case is to create a record and expect the created record or the id in return. In this case what you can do is to tell rspec what you expect from the call to your dependency.

For example:

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

  def db
    FakeDB
  end

  it "returns the id of the created record" do
    catalog = BooksCatalog.new(db)

    # This is how you can use rspec to define a response.
    allow(db).to receive(:insert).and_return(id: "book-1234", name: "Harry Potter")

    book_id = catalog.add_book("Harry Potter")
    expect(book_id).to eq "book-1234"
  end
end

These are some basic examples, and in some cases you are going to need something more complex, but really you can do a lot just with this knowledge =)

Related articles

Weekly tips and tools for Ruby on Rails developers

I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.