Search code examples
rubyunit-testingrspecrspec3

Enabling expect syntax within a class


In rspec 3.1.0, a spec can use #allow to stub a method:

describe do
  specify do
    o = Object.new
    allow(o).to receive(:foo)
    o.foo
  end
end

This works fine. However, if the stub is within a method in a class, then the #allow method is not defined:

describe do

  class Baz
    def allow_bar(o)
      allow(o).to receive(:bar)
    end
  end

  specify do
    o = Object.new
    Baz.new.allow_bar(o)
    o.bar
  end

end

The error is:

 Failure/Error: allow(o).to receive(:bar)
 NoMethodError:
   undefined method `allow' for #<Baz:0x8de6720>
 # ./bar_spec.rb:5:in `allow_bar'
 # ./bar_spec.rb:11:in `block (2 levels) in <top (required)>'

Why I am stubbing within a class

The test defines its test double as a regular class rather than using rspec's "double" method. This is because the test double has a thread. Inside the test double is this code:

if command == :close
  # Note: Uses the old (rspec 2) syntax.  Needs to be converted
  # to rspec 3 syntax using the #allow method.
  socket.stub(:getpeername).and_raise(RuntimeError, "Socket closed")
end

This prevents the code under test from erroneously using the socket after the session has been closed.

A private solution

I can give the test double access to #allow by calling a private API in rspec-mock:

  class Baz
    RSpec::Mocks::Syntax.enable_expect self    # Uses private API
    def allow_bar(o)
      allow(o).to receive(:bar)
    end
  end

This works. However, it is explicitly marked as a private API in rspec/mocks/syntax.rb:

  # @api private
  # Enables the expect syntax (`expect(dbl).to receive`, `allow(dbl).to receive`, etc).
  def self.enable_expect(syntax_host = ::RSpec::Mocks::ExampleMethods)
  ...

The Question

In Rspec 3.1, is there a public API I can use to make the expect syntax available within a class?


Solution

  • You can accomplish this by mixing in a few RSpec modules/classes into your class:

    class MyClass
      include RSpec::Mocks::ExampleMethods::ExpectHost
      include RSpec::Matchers 
    
      def some_test
        expect(1).to eql(1)
      end
    end