Orangutan

Orangutan is a library for creating stubs.

It allows you to create stub objects and to specify methods to return and yield values as well as to raise errors.

Originally it was intended for use in testing .net code using ironruby. I’m now using it to test pure ruby code as I prefer the syntax over the alternatives.

At this stage only the simplest of .net clr interfaces can be stubbed. Eventually you should be able to test java classes (with jruby) in exactly the same way.

Pure stub objects can be created which have no methods - they simply record any method invocations. Methods of existing objects (including classes) can also be stubbed.

It works by recording all method calls so that assertions can be made about them after the fact and providing a simple dsl for setting up stubbed methods (which can be used as expectations to assert on later).

All method invocations are reported to the one central array so you can assert method invocations occur across different objects (not that you should want to).

Installation:

gem install orangutan

Some examples of usage (note that these are not in the context of any testing framework and are intended only to indicate how to use the api):

require 'orangutan'

include Orangutan::Chantek

reset_stubs

Creating a pure ruby stub

@stub = stub :stub

Creating a stub that implements a clr interface

@clr_stub = stub :clr_stub, :clr_interface => System::IDisposable

Setting up stub methods that return values

so_when(:stub).receives(:execute).with(7).return('baz')

Setting up stub methods that yield values

so_when(:stub).receives(:execute).with(7).yield('baz')

Setting up stub methods that raise errors

so_when(:stub).receives(:execute).with(7).raise('baz')

checking whether an expectation was met

expectation = so_when(:stub).receives(:execute).with(7)
puts expectation.matched? (=> false)
@stub.execute(7)
puts expectation.matched? (=> false)

setting limits on expectations

so_when(:stub).receives(:execute).with(7).once
so_when(:stub).receives(:execute).with(7).twice
so_when(:stub).receives(:execute).with(7).exactly(4).times

Note that once an expectation has reached its limit, it will no longer match. Expections will match in the order they are specified. This mechanism can be used to set up more complex expected call sequences:

e1 = so_when(:stub).receives(:execute).with(7).return('ok').once
e2 = so_when(:stub).receives(:execute).return('hmm').twice

puts e1.count (=> nil)
puts e2.count (=> nil)
@stub.execute(7) (=> 'ok')
puts e1.count (=> 1)
puts e2.count (=> nil)
@stub.execute(7) (=> 'hmm')
puts e1.count (=> 1)
puts e2.count (=> 1)
@stub.execute(7) (=> 'hmm')
puts e1.count (=> 1)
puts e2.count (=> 2)
@stub.execute(7) (=> nil)
puts e1.count (=> 1)
puts e2.count (=> 2)
puts e1.matched? (=> true)
puts e2.matched? (=> true)

Checking all expectations

expectations.any? {|e| !e.matched? }

Naming expectations

Sometimes you’ll just want to verify that a stub expectation was actually matched:

so_when(:stub, :as => :execute_with_seven).receives(:execute).with(7).return('ok').once
raise "wasn't matched" unless expectation(:execute_with_seven).matched? # verifying explicitly
expectation(:execute_with_seven).should be_matched # verifying expectation (using rspec matchers)

Examining recorded method calls

calls.each {|call| puts "#{call.name}.#{call.method}(#{call.args.join(',')})" }

Recorded call sequences can also be used for assertions in combination with or instead of checking expectations.

Stubbing methods on object instances

register_object :math, Math   # registers the object (this just gives the object a name for referencing in calls)
stub_method :math, :cos       # replaces the implementation of the cos method with a recorder
so_when(:math).receives(:cos).with(0).return("i just can't take it anymore")

restore_methods # restores any changes made to methods (essential for any class methods that have been stubbed)

Registering as a mock framework with rspec

require 'orangutan/mock_adapter'
Spec::Runner.configure do |config|
  config.mock_with Orangutan::MockAdapter
end

Questions:

Why Orangutan?

My daughter likes orangutans and I couldn’t think of a better name. Both Sumatran and Bornean orangutans are endangered species so even if you intensely dislike this project or its implementation, at least you can be made aware of the plight of these spectacular creatures.

Did it have anything to do with Clyde in “Every Which Way But Loose”

Definately not. What a ridiculous question. I’m appalled.

What’s Chantek?

Chantek is a famous orangutan that can solve sudokus and the rubik’s cube - en.wikipedia.org/wiki/Chantek

Why would you create yet another mocking framework?

Pretty much for the sake of it. Originally it was an IronRuby contribution but Ivan Porto Carrera has provided a much better mocking solution for that in caricature (see github.com/casualjim/caricature). Now it’s just an extremely interesting exercise in learning about metaprogramming in ruby.