Mocking

Mock with rspec

RSpec uses its own mocking framework by default. You can also configure it
explicitly if you wish.Scenarios

  1. Passing message expectation
  2. Failing message expectation
  3. Failing message expectation in pending example (remains pending)
  4. Passing message expectation in pending example (fails)
  5. Accessing `RSpec.configuration.mock_framework.framework_name`
  6. Doubles may be used in generated descriptions

Passing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rspec
end

RSpec.describe "mocking with RSpec" do
  it "passes when it should" do
    receiver = double('receiver')
    expect(receiver).to receive(:message)
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe examples should all passFailing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rspec
end

RSpec.describe "mocking with RSpec" do
  it "fails when it should" do
    receiver = double('receiver')
    expect(receiver).to receive(:message)
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 1 failure”Failing message expectation in pending example (remains pending)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rspec
end

RSpec.describe "failed message expectation in a pending example" do
  it "is listed as pending" do
    pending
    receiver = double('receiver')
    expect(receiver).to receive(:message)
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 0 failures, 1 pending”Andthe exit status should be 0Passing message expectation in pending example (fails)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rspec
end

RSpec.describe "passing message expectation in a pending example" do
  it "fails with FIXED" do
    pending
    receiver = double('receiver')
    expect(receiver).to receive(:message)
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “FIXED”Thenthe output should contain “1 example, 1 failure”Andthe exit status should be 1Accessing `RSpec.configuration.mock_framework.framework_name`Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rspec
end

RSpec.describe "RSpec.configuration.mock_framework.framework_name" do
  it "returns :rspec" do
    expect(RSpec.configuration.mock_framework.framework_name).to eq(:rspec)
  end
end

WhenI run rspec example_spec.rbThenthe examples should all passDoubles may be used in generated descriptionsGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rspec
end

RSpec.describe "Testing" do
  # Examples with no descriptions will default to RSpec-generated descriptions
  it do
    foo = double("Test")
    expect(foo).to be foo
  end
end

WhenI run rspec example_spec.rbThenthe examples should all pass

Mock with mocha

Configure RSpec to use mocha as shown in the scenarios below.Scenarios

  1. Passing message expectation
  2. Failing message expectation
  3. Failing message expectation in pending example (remains pending)
  4. Passing message expectation in pending example (fails)
  5. Accessing `RSpec.configuration.mock_framework.framework_name`

Passing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :mocha
end

RSpec.describe "mocking with RSpec" do
  it "passes when it should" do
    receiver = mock('receiver')
    receiver.expects(:message).once
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe examples should all passFailing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :mocha
end

RSpec.describe "mocking with RSpec" do
  it "fails when it should" do
    receiver = mock('receiver')
    receiver.expects(:message).once
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 1 failure”Failing message expectation in pending example (remains pending)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :mocha
end

RSpec.describe "failed message expectation in a pending example" do
  it "is listed as pending" do
    pending
    receiver = mock('receiver')
    receiver.expects(:message).once
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 0 failures, 1 pending”Andthe exit status should be 0Passing message expectation in pending example (fails)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :mocha
end

RSpec.describe "passing message expectation in a pending example" do
  it "fails with FIXED" do
    pending
    receiver = mock('receiver')
    receiver.expects(:message).once
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “FIXED”Thenthe output should contain “1 example, 1 failure”Andthe exit status should be 1Accessing `RSpec.configuration.mock_framework.framework_name`Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :mocha
end

RSpec.describe "RSpec.configuration.mock_framework.framework_name" do
  it "returns :mocha" do
    expect(RSpec.configuration.mock_framework.framework_name).to eq(:mocha)
  end
end

WhenI run rspec example_spec.rbThenthe examples should all pass

Mock with flexmock

Configure RSpec to use flexmock as shown in the scenarios below.Scenarios

  1. Passing message expectation
  2. Failing message expectation
  3. Failing message expectation in pending example (remains pending)
  4. Passing message expectation in pending example (fails)
  5. Accessing `RSpec.configuration.mock_framework.framework_name`

Passing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :flexmock
end

RSpec.describe "mocking with Flexmock" do
  it "passes when it should" do
    receiver = flexmock('receiver')
    receiver.should_receive(:message).once
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe examples should all passFailing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :flexmock
end

RSpec.describe "mocking with Flexmock" do
  it "fails when it should" do
    receiver = flexmock('receiver')
    receiver.should_receive(:message).once
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 1 failure”Failing message expectation in pending example (remains pending)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :flexmock
end

RSpec.describe "failed message expectation in a pending example" do
  it "is listed as pending" do
    pending
    receiver = flexmock('receiver')
    receiver.should_receive(:message).once
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 0 failures, 1 pending”Andthe exit status should be 0Passing message expectation in pending example (fails)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :flexmock
end

RSpec.describe "passing message expectation in a pending example" do
  it "fails with FIXED" do
    pending
    receiver = flexmock('receiver')
    receiver.should_receive(:message).once
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “FIXED”Thenthe output should contain “1 example, 1 failure”Andthe exit status should be 1Accessing `RSpec.configuration.mock_framework.framework_name`Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :flexmock
end

RSpec.describe "RSpec.configuration.mock_framework.framework_name" do
  it "returns :flexmock" do
    expect(RSpec.configuration.mock_framework.framework_name).to eq(:flexmock)
  end
end

WhenI run rspec example_spec.rbThenthe examples should all pass

Mock with rr

Configure RSpec to use rr as shown in the scenarios below.Scenarios

  1. Passing message expectation
  2. Failing message expectation
  3. Failing message expectation in pending example (remains pending)
  4. Passing message expectation in pending example (fails)
  5. Accessing `RSpec.configuration.mock_framework.framework_name`

Passing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rr
end

RSpec.describe "mocking with RSpec" do
  it "passes when it should" do
    receiver = Object.new
    mock(receiver).message
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe examples should all passFailing message expectationGivena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rr
end

RSpec.describe "mocking with RSpec" do
  it "fails when it should" do
    receiver = Object.new
    mock(receiver).message
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 1 failure”Failing message expectation in pending example (remains pending)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rr
end

RSpec.describe "failed message expectation in a pending example" do
  it "is listed as pending" do
    pending
    receiver = Object.new
    mock(receiver).message
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “1 example, 0 failures, 1 pending”Andthe exit status should be 0Passing message expectation in pending example (fails)Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rr
end

RSpec.describe "passing message expectation in a pending example" do
  it "fails with FIXED" do
    pending
    receiver = Object.new
    mock(receiver).message
    receiver.message
  end
end

WhenI run rspec example_spec.rbThenthe output should contain “FIXED”Thenthe output should contain “1 example, 1 failure”Andthe exit status should be 1Accessing `RSpec.configuration.mock_framework.framework_name`Givena file named “example_spec.rb” with:

RSpec.configure do |config|
  config.mock_with :rr
end

RSpec.describe "RSpec.configuration.mock_framework.framework_name" do
  it "returns :rr" do
    expect(RSpec.configuration.mock_framework.framework_name).to eq(:rr)
  end
end

WhenI run rspec example_spec.rbThenthe examples should all pass

Mock with an alternative framework

In addition to rspec, mocha, flexmock, and RR, you can choose an alternate
framework as the mocking framework. You (or the framework authors) just needs
to provide an adapter that hooks RSpec’s events into those of the framework.

A mock framework adapter must expose three methods:

  • setup_mocks_for_rspec
    • called before each example is run
  • verify_mocks_for_rspec
    • called after each example is run
    • this is where message expectation failures should result in an error with the appropriate failure message
  • teardown_mocks_for_rspec
    • called after verify_mocks_for_rspec
    • use this to clean up resources, restore objects to earlier state, etc
    • guaranteed to run even if there are failures

ScenariosMock with alternate frameworkGivena file named “expector.rb” with:

class Expector
  class << self
    def expectors
      @expectors ||= []
    end

    def clear_expectors
      expectors.clear
    end

    def verify_expectors
      expectors.each {|d| d.verify}
    end
  end

  def initialize
    self.class.expectors << self
  end

  def expectations
    @expectations ||= []
  end

  def expect(message)
    expectations << message.to_s
  end

  def verify
    unless expectations.empty?
      raise expectations.map {|e|
        "expected #{e}, but it was never received"
      }.join("\n")
    end
  end

private

  def method_missing(name, *args, &block)
    expectations.delete(name.to_s)
  end

public

  module RSpecAdapter
    def setup_mocks_for_rspec
      # no setup necessary
    end

    def verify_mocks_for_rspec
      Expector.verify_expectors
    end

    def teardown_mocks_for_rspec
      Expector.clear_expectors
    end
  end
end

Givena file named “example_spec.rb” with:

require File.expand_path("../expector", __FILE__)

RSpec.configure do |config|
  config.mock_with Expector::RSpecAdapter
end

RSpec.describe Expector do
  it "passes when message is received" do
    expector = Expector.new
    expector.expect(:foo)
    expector.foo
  end

  it "fails when message is received" do
    expector = Expector.new
    expector.expect(:foo)
  end
end

WhenI run rspec example_spec.rb --format docThenthe exit status should be 1Andthe output should contain “2 examples, 1 failure”Andthe output should contain “fails when message is received (FAILED – 1)”