RubyGuides
Share this post!

How to Use RSpec Mocks (Step-By-Step Tutorial)

What is a mock in RSpec?

(Or a mock in general, because this isn’t a concept unique to RSpec.)

A mock is an object used for testing.

You use mocks to test the interaction between two objects.

For example:

You’re writing an API that flips images.

Instead of writing your own image-manipulation code you use a gem like mini_magick.

You want to test the interaction between your code & this external dependency… so you write a mock that expects the correct methods to be called on the ImageProcessor class.

This means that you won’t be flipping images (a slow operation) every time you run your tests.

How does that work?

Mocks replace the original object, so the real methods won’t be called.

Let’s see code examples!

RSpec Mock Examples

Here’s the ImageFlipper test:

RSpec.describe "ImageFlipper" do
  it "calls the flip method with the correct arguments" do
    mock = double("mini_magick")
    expect(mock).to receive(:flip).with("ruby.jpg")

    img = ImageFlipper.new(mock)
    img.flip("ruby.jpg")
  end
end

Now if we run this test we can code along TDD style.

First we need to write an ImageFlipper class.

Like this:

class ImageFlipper
  def initialize(image_processor)
    @image_processor = image_processor
  end
end

We also need a flip method:

def flip(file_name)
end

Now we get this feedback from RSpec:

Failures:

1) ImageFlipper calls the flip method with the correct arguments
   Failure/Error: expect(mock).to receive(:flip).with("ruby.jpg")

     (Double "mini_magick").flip("ruby.jpg")
         expected: 1 time with arguments: ("ruby.jpg")
         received: 0 times
   # ./rspec-mocks.rb:6:in `block (2 levels) in <top (required)>'

This is saying that the method was called 0 times, but it was expected to be called 1 time.

You can make this test pass by giving it what it wants:

def flip(file_name)
  @image_processor.flip(file_name)
end

And there you go, we have a passing test:

.

Finished in 0.00751 seconds (files took 0.11157 seconds to load)
1 example, 0 failures

What have we done here?

We have created a class that given an image_processor class that responds to the flip method, knows how to get the job done.

This example is simple, but you can imagine a complete implementation of ImageFlipper that checks if the file exists, if it’s a valid image, etc.

In a regular test you check the return value of a method:

“Did this method return a flipped image?”

When using a mock you are testing the behavior:

“Did we tell others the right thing to do, with the right information, and exactly the right amount of times that we need?”

Mocks vs Stubs

Another confusion point is about comparing mocks & stubs.

What is the difference?

  • A stub is only a method with a canned response, it doesn’t care about behavior.
  • A mock expects methods to be called, if they are not called the test will fail.

Here’s a stub in RSpec:

stub = double("json")

allow(stub).to receive(:response) do
  {"blog"=>"rubyguides.com", "rating"=>"5/5"}.to_json
end

The allow method is what makes this a stub.

We are allowing our testing object double("json") to receive & respond to this method, but we aren’t checking if it’s being called.

That’s the difference!

How to Use Verified Doubles

One of the downsides of mocks & stubs is that you can end up using a method that doesn’t exist in your production code.

Because the method name has changed… or maybe you made a typo!

Meet verified doubles.

A verified double can be used as either a stub (allow) or a mock (expect) & it will check that a method with this name exists.

Example:

mock = instance_double(ImageProcessor)

This will raise an error if the method doesn’t exist:

1) ImageFlipper calls the flip method with the correct arguments
     Failure/Error: expect(mock).to receive(:flip).with("ruby.jpg")
       the ImageProcessor class does not implement the instance method: flip

But it will work correctly if the method exist.

Mocks Returning Values

Let’s go back to mocking.

In the last example we had this:

expect(mock).to receive(:flip).with("ruby.jpg")

When your code calls flip, the mock will return nil.

If the code is expecting some value other than nil then this will result in an error.

You can fix this by making the mock return a result.

Like this:

expect(mock).to receive(:flip).with("ruby.jpg").and_return("ruby-flipped.jpg")

How to Mock Instance Methods

Let’s say that you have code like this:

class NumberGenerator
  def random
    "A" * rand(1..10)
  end
end

This method is hard to test because of the randomness.

RSpec allows you to mock, or stub rand.

Like this:

it "generates a random number" do
  generator = NumberGenerator.new

  allow(generator).to receive(:rand).and_return(5)

  expect(generator.random).to eq("AAAAA")
end

Now:

rand returns a fixed value so you can use it for testing the outcome of your method.

Ideally, you would want to inject your dependency (the rand function, in this case) so you can control it. Injecting a dependency means that you pass it in as a parameter, nothing fancy.

But sometimes it is more convenient to just stub the method.

When to Use Mocks?

Now for the BIG question…

Exactly when should you use mocks?

Software development is a complex topic.

But there are some guidelines you can follow:

  1. If the method under test returns a value & it has no side effects (creating files, making API requests, etc.) then you don’t need a mock. Just check for the return value.
  2. If the method is working with external objects & sending orders to them, then you can mock the interactions with these objects.
  3. If the method is REQUESTING data from an external service (like an API), then you can use a stub to provide this data for testing purposes.

You want to reserve mocking for interactions with the outside world.

In other words…

Avoid mocking your own application’s classes!

Why?

Because that promotes coupling your tests with implementation details & it makes your code harder to change.

The only exception is for classes that are wrappers for 3rd party code.

Watch this video to learn more:

Summary

You have learned about RSpec mocks, stubs & verified doubles!

Please share this article so more people can enjoy & benefit from this content.

Thanks for reading 🙂

Leave a Comment: