DevTeam Blog

A few tips to speed up RSPEC: Stubbing and Mocking

23 Apr 2014

By Gustavo Garcia G

A few days ago, I took a course about RSPEC. This post is about the two things I enjoyed most: Stubbing and Mocking.

Stubbing is a way to fake a call to a method. Sometimes we need to test a method A that calls another (very huge) method B inside, and testing method A should test just method A, not method B, right?

Mocking is an expectation of a Stub getting called.

Let’s see some code now.

Stubbing

We are going to asume for now that we have the following class Article:

class Article < ActiveRecord::Base
  ...
  def schedule
    self.export
    self.scheduled = true
  end

  def export
    #A very expensive method    
  end
  ...

Now, this is how the schedule test should look like:

describe Article do
  context "#schedule" do
    it "sets scheduled as true" do
      article = FactoryGirl.create(:article)
      article.schedule
      expect(article.scheduled).to be true
    end
  end
end

The test will probably pass, but we are calling the expensive method export for this test… is this really necessary? Maybe not. Here is where we could use a Stub, just like this:

describe Article do
  context "#schedule" do
    it "sets scheduled as true" do
      article = FactoryGirl.create(:article)
      allow(article).to receive(:export)
      article.schedule
      expect(article.scheduled).to be true
    end
  end
end

The test will still pass, but we feel smarter now, because we are not calling the real method export. What do we gain? A faster test suit.

Ok, but this was an easy escenario, let’s asume this Article class refactorization:

class Article < ActiveRecord::Base
  ...
  def schedule
    self.scheduled = self.export
  end

  def export
    #A very expensive method that returns true/false, depending of success or failure at exporting
    ...    
  end
  ...

Our previous stub will make the test fail, because our fake method isn’t returnig true, so… let’s fix it!

describe Article do
  context "#schedule" do
    it "sets scheduled as true" do
      article = FactoryGirl.create(:article)
      allow(article).to receive(:export).and_return(true)
      expect(article.scheduled).to be true
      article.schedule
    end
  end
end

Voila! The test pass again. Note that you can specify anything as a return value. In this case we used a boolean, but I have used OpenStruct too.

Mockings

In some cases, we need more than overwritting a method, we need to make sure that this fake method is being called, here is where we use Mocks.

In the case we need to make sure the method export is being called in certain scenario, we can try this

describe Article do
  context "#schedule" do
    it "sets scheduled as true" do
      article = FactoryGirl.create(:article)
      allow(article).to receive(:export).and_return(true)
      expect(article).to receive(:export)
      article.schedule
    end
  end
end

We can be even more specific, an use *receive(:export).exactly(N).times, like this:

describe Article do
  context "#schedule" do
    it "sets scheduled as true" do
      article = FactoryGirl.create(:article)
      allow(article).to receive(:export).and_return(true)
      expect(article).to receive(:export).exactly(1).times
      article.schedule
    end
  end
end

This mockings are very useful when you have conditions inside your methods, so you can test some scenarios with different conditions and test how many times the method is called.

Another hint is that you can also stub/mock static methods, like this:

class Article < ActiveRecord::Base
  ...
  def schedule
    self.export
    self.scheduled = true
  end

  def export
    #A very expensive method    
  end

  self.get_exported
    #a static method
  end

And the test:

describe Article do
  context "#schedule" do
    it "sets scheduled as true" do
      article = FactoryGirl.create(:article)
      allow(Article).to receive(:get_exported)
      expect(Article).to receive(:get_exported).exactly(1).times
      article.schedule
    end
  end
end

Extra Tips

1 - To mark a test as pending, you can just insert a “x” before the “it” statement, like this:

xit "sets scheduled as true" do

2 - To run just a specific test, you can run this command:

bin/rspec spec/models/article_spec.rb:X

Where X is the number line of the specific test

comments powered by Disqus