Skip to content
By Riana Ferreira

How to toe the line with RSpec, Stubs, and RuboCop

Recently I set up the Rubocop gem for a project. I wanted to use it to ​ensure that my code base aligned with the community Ruby Style Guide. When I ran the tool, I came up against the following error message:

spec/interactors/allow_box_to_be_reused_spec.rb:132:9: C: Avoid stubbing using allow_any_instance_of.
allow_any_instance_of(AllowBoxToBeReused).to receive(:recycle_box).

This message didn't give me any idea of what the problem was, nor what I should do to avoid it in the future.

As there is no generic solution, the code under test will help you to determine what needs to be done. In order to do this you need to:

  • understand how rspec mocking and stubbing works.
  • be clear on what you are testing.
  • clean up your code.

This was the class under test:

class AllowBoxToBeReused
  include Interactor

  delegate :box, to: :context

  def call
    context_failed_message unless recycle_box
  end

  private

  def recycle_box
    box.update(content: nil)
  end

  def context_failed_message
    context.fail!(
      message: I18n.t(".errors.box_can_not_be_reused"),
    )
  end

end

And this was the problematic spec:

context box recycling fails do

  subject(:context) do
    AllowBoxToBeReused.call(box: box)
  end

  let(:box) { FactoryGirl.build(:box, :with_content)

  before do
    allow_any_instance_of(AllowBoxToBeReused).to receive(:recycle_box).and_return(false)
  end

  it "fails to update" do
    expect(context.failure?).to be_truthy
  end

end

What is wrong with this spec?

The example manipulates the internal behaviour of the class to make it respond in a certain way without testing the actual code. In other words, I was making too many assumptions about how the class actually works.

When testing a class, you should test how the code works only by controlling the external dependencies.

In this example, the class has an external dependency on the Box class. When the update method is invoked for an instance of the Box class, it expects a response of:

  • false, if the changes to the object can't be saved
  • true, if the changes to the object are saved

This is what I should have been testing!

A better spec

Here is an example of a much better spec that obeys these principles:

context box recycling fails do

  subject(:context) do
    AllowBoxToBeReused.call(box: box)
  end

  let(:box) { object_double(Box.new, update: false) }

  it "fails to update" do
    expect(context.failure?).to be_truthy
  end

end

Why is this better?

The stubbed external dependency is being passed to the class under test and its actual behaviour is being tested. This allows you to explicitly handle the new behaviour, refactor your code and add more specs to clearly document the new behaviour.

(As a side benefit, sometimes this can uncover unexpected behaviour for the class under test. :-))

You are creating a stubbed class instance object, and ensuring that it actually responds to the method it receives.

Most importantly RuboCop stops complaining!

Finally, for your reading pleasure, here is a handy rspec reference.

Latest Articles by Our Team

Our expert team of designers and developers love what the do and enjoy sharing their knowledge with the world.

We Hire Only the Best

reinteractive is Australia’s largest dedicated Ruby on Rails development company. We don’t cut corners and we know what we are doing.

We are an organisation made up of amazing individuals and we take pride in our team. We are 100% remote work enabling us to choose the best talent no matter which part of the country they live in. reinteractive is dedicated to making it a great place for any developer to work.

Free Community Workshops

We created the Ruby on Rails InstallFest and Ruby on Rails Development Hub to help introduce new people to software development and to help existing developers hone their skills. These workshops provide invaluable mentorship to train developers, addressing key skills shortages in the industry. Software development is a great career choice for all ages and these events help you get started and skilled up.

  • Installfest

    Installfest

    The Ruby on Rails Installfest includes a full setup of your development environment and step-by-step instructions on how to build your first app hosted on Heroku. Over 1,800 attendees to date and counting.

    Learn more about Installfest

  • Development Hub

    Development Hub

    The Ruby on Rails Development Hub is a monthly event where you will get the chance to spend time with our team and others in the community to improve and hone your Ruby on Rails skills.

    Learn more about Development Hub

  • Webinars

    Webinars

    Webinars are our online portal for tips, tricks and lessons learned in everything we do. Make the most of this free resource to help you become a better developer.

    Learn more about webinars

Get the “reinteractive Review” Monthly Email