Skip to content

Instantly share code, notes, and snippets.

@andhapp
Last active August 29, 2015 13:56
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save andhapp/9088908 to your computer and use it in GitHub Desktop.
Save andhapp/9088908 to your computer and use it in GitHub Desktop.
stub v should_receive
## stub v should_receive
## This is just a simple example to show uses of stub, and should_receive. Imagine you have the following code:
# topic.rb
class Topic < ActiveRecord::Base
has_many :stories
def add_story(story)
unless exists? story
update_stories_association(story)
end
end
def exists?(story)
.... # some code to test if the story exists
end
def update_stories_association
.... # some code to update story association
end
end
# story.rb
class Story < ActiveRecord::Base
belongs_to :topic
end
# topic_spec.rb
describe Topic do
# Here the intent is to just test the add_story is behaving in the way it does
# and in order to test both the case when story exists and story doesn't exist
# we will stub (fake) the exists? method to return what we want it to return
describe '#add_story' do
it 'does not add already associated story' do
topic = Topic.new
story = Story.new
# this tests the case when the story exists, the exists? method could go and talk to database
# but we don't care about it whilst testing add_story, we just ensure that the add_story behaves
# as expected for when the topic exits
topic.stub(:exists? => true)
topic.add_story(story)
# should_receive on the other hand will test what you expect to happen
topic.should_receive(:update_stories_association).never
end
it 'adds a new story' do
topic = Topic.new
story = Story.new
topic.stub(:exists? => false)
topic.add_story(story)
# should_receive on the other hand will test what you expect to happen
topic.should_receive(:update_stories_association).once
end
end
end
@jamesrwhite
Copy link

Ok that makes a bit more sense now, thanks.

One method I'm having a lot of trouble testing at the moment is this:

# Starts EventMachine in a new thread if it isn't already running
def ensure_em_running
  Thread.new { EM.run } unless EM.reactor_running?
  sleep 0.1 until EM.reactor_running?
end

This is what I have at the moment:

describe '#ensure_em_running' do
  context 'when eventmachine is not running' do
    it 'should start eventmachine' do
      eventmachine.stub(:reactor_running?).and_return(false, true)
      eventmachine.stub(:run)
      eventmachine.should_receive(:reactor_running?)
      eventmachine.should_receive(:run)

      ensure_em_running
    end
  end
end

It seems to work some of the time but not others, because I'm starting EM in a new thread perhaps?

@andhapp
Copy link
Author

andhapp commented Feb 20, 2014

Your spec should be like the code below. To answer why, let's look at your spec in a bit more detail:

You are testing 'ensure_em_running', and testing the case when eventmachine is not running, which means you want reactor_running? to return false, you've explicitly made this choice and that's what you stub, the context you are starting with. With that context, when you call ensure_em_running, you expect the run method to be called.

describe '#ensure_em_running' do
  context 'when eventmachine is not running' do
    it 'should start eventmachine' do
      eventmachine.stub(:reactor_running?).and_return(false)

      eventmachine.should_receive(:run)

      ensure_em_running
    end
  end
end

Yes, multithreaded programs are hard to write, and even harder to test.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment