Skip to content

Instantly share code, notes, and snippets.

@kognate
Forked from jonathanpenn/rdingus.rb
Created December 12, 2008 14:24
Show Gist options
  • Save kognate/35135 to your computer and use it in GitHub Desktop.
Save kognate/35135 to your computer and use it in GitHub Desktop.
require 'rubygems'
require 'spec'
class RDingus
def initialize(parent = nil, invocation = nil)
@parent = parent
@invocation = invocation
@invocation_exceptions = InvocationList.new
@invocation_expectations = InvocationList.new
@invocation_expectation_set = InvocationList.new
@invocations = []
end
def method_missing(name, *args)
invocation = InvocationRecord.new(name, args)
@invocations << invocation
raise @invocation_exceptions[invocation] if @invocation_exceptions[invocation]
if @invocation_expectation_set[invocation]
@invocation_expectations[invocation]
else
@invocation_expectation_set[invocation] = true
@invocation_expectations[invocation] = RDingus.new(self, invocation)
end
end
def returns(value)
@parent.return_value_hook(value, @invocation)
end
def raises(exception)
@parent.raise_value_hook(exception, @invocation)
end
def calls
@invocations
end
def has_received?(method)
@invocations.any? do |invocation|
invocation.method == method
end
end
protected
def return_value_hook(value, invocation)
@invocations.pop
@parent.return_value_hook(value, invocation) if @parent
@invocation_expectations[invocation] = value
end
def raise_value_hook(value, invocation)
@invocations.pop
@parent.raise_value_hook(value, invocation) if @parent
@invocation_exceptions[invocation] = value
end
public
class InvocationRecord
attr_reader :method, :args
attr_accessor :value
def initialize(method, args = [], *optional)
@method = method
@args = args
@value = optional.first
@has_value = !optional.empty?
end
def value=(v)
@has_value = true
@value = v
end
def has_value?
@has_value
end
def eql?(b)
self.method == b.method && ((self.args == b.args) || self.any? || b.any?)
end
def ==(b)
self.eql?(b)
end
def any?
@args && @args.first == :any
end
def to_s
"#{method}(#{args.map{|i|i.inspect}.join(", ")})"
end
def inspect
retval = @has_value ? " => #{@value.inspect}" : ""
"#<Invocation \"#{self}\"#{retval}>"
end
end
class InvocationList
def initialize
@list = []
end
def []=(invocation, value)
@list << [invocation, value]
value
end
def [](invocation)
@list.reverse.each do |inv, value|
return value if inv == invocation
end
nil
end
end
end
describe RDingus do
before :each do
@rdingus = RDingus.new
end
it "should return an RDingus on method calls" do
@rdingus.a_method.should be_kind_of(RDingus)
end
it "should return the same RDingus for the same method invocation" do
@rdingus.a_method.should == @rdingus.a_method
end
it "should return a different RDingus for a different method" do
@rdingus.a_method.should_not == @rdingus.b_method
end
it "should return a different RDingus for the same method invocation but with different arguments" do
@rdingus.a_method.should_not == @rdingus.a_method(:argument)
end
describe "when specifiying return values" do
it "the specification should return the value expected" do
@rdingus.a_method.returns(:shell).should == :shell
end
it "should return what's assigned" do
@rdingus.a_method.returns :shell
@rdingus.a_method.should == :shell
end
it "should return false if assigned" do
@rdingus.a_method.returns false
@rdingus.a_method.should == false
end
it "should return nil if assigned" do
@rdingus.a_method.returns nil
@rdingus.a_method.should == nil
end
it "should not remember as a method call" do
@rdingus.a_method.returns "something"
@rdingus.calls.should be_empty
end
it "should not forget other method calls" do
@rdingus.remember_me
@rdingus.a_method.returns "something"
@rdingus.calls.map{|i|i.method}.should == [:remember_me]
end
describe "of nested method calls" do
it "should return what's assigned" do
@rdingus.a_method.b_method.returns :stump
@rdingus.a_method.b_method.should == :stump
end
it "should not remember as a method call" do
@rdingus.a_method.b_method.returns "something"
@rdingus.calls.should be_empty
end
end
describe "with arbitrary arguments" do
before :each do
@rdingus.a_method(:any).returns :stump
end
it "should return what's assigned given any arguments" do
@rdingus.a_method(:argument).should == :stump
end
it "should return what's assigned with no arguments" do
@rdingus.a_method.should == :stump
end
end
describe "with specific arguments" do
before :each do
@rdingus.a_method(:argument).returns :stump
end
it "should return what's assigned" do
@rdingus.a_method(:argument).should == :stump
end
it "should return an RDingus if arguments don't match" do
@rdingus.a_method(:argument, :argument2).should be_kind_of(RDingus)
end
end
end
describe "when specifying exceptions" do
it "should raise given exception" do
@rdingus.raise_me.raises "Exception"
lambda { @rdingus.raise_me }.should raise_error("Exception")
end
it "should not remember as a method call" do
@rdingus.raise_me.raises "Exception"
lambda { @rdingus.raise_me }.should raise_error("Exception")
@rdingus.calls.map{|i|i.method}.should == [:raise_me]
end
it "should not forget other method calls" do
@rdingus.remember_me
@rdingus.raise_me.raises "Exception"
lambda { @rdingus.raise_me }.should raise_error("Exception")
@rdingus.calls.map{|i|i.method}.should == [:remember_me, :raise_me]
end
end
describe "recording invocations" do
it "should remember methods invoked" do
@rdingus.sandwich
@rdingus.calls.first.method.should == :sandwich
end
it "should remember with arguments invoked" do
@rdingus.sandwich(:with_cheese)
@rdingus.calls.first.args.first.should == :with_cheese
end
end
end
describe RDingus::InvocationRecord do
before :each do
@invocation_record = RDingus::InvocationRecord.new(:method, [:arg1, :arg2])
end
it "should be equal for same method and arguments" do
RDingus::InvocationRecord.new(:method, [:arg1, :arg2]).should == @invocation_record
end
it "should not be equal for different method" do
RDingus::InvocationRecord.new(:method2, [:arg1, :arg2]).should_not == @invocation_record
end
it "should not be equal for different arguments" do
RDingus::InvocationRecord.new(:method, [:arg2, :arg3]).should_not == @invocation_record
end
it "should define == the same as eql?" do
RDingus::InvocationRecord.new(:method, [:arg1, :arg2]).should be_eql(@invocation_record)
end
describe "when told to accept any arguments" do
before :each do
@invocation_record = RDingus::InvocationRecord.new(:method, [:any])
end
it "should be equal for same method and same arguments" do
RDingus::InvocationRecord.new(:method, [:any]).should == @invocation_record
end
it "should be equal for same method and different arguments" do
RDingus::InvocationRecord.new(:method, [:other, :args]).should == @invocation_record
end
it "should not be equal for different method" do
RDingus::InvocationRecord.new(:method2, [:arg1, :arg2]).should_not == @invocation_record
end
end
it "should convert to a useful string" do
@invocation_record.to_s.should == "method(:arg1, :arg2)"
end
it "should have a useful inspect result" do
@invocation_record.value = :arg3
@invocation_record.inspect.should == "#<Invocation \"method(:arg1, :arg2)\" => :arg3>"
end
end
describe RDingus::InvocationList do
before :each do
@invocation = RDingus::InvocationRecord.new(:method, [:arg1, :arg2])
@invocation_list = RDingus::InvocationList.new
end
it "should store and retrieve for same invocation" do
@invocation_list[@invocation] = "test"
@invocation_list[@invocation].should == "test"
end
it "should store and retrieve for equivalent invocations" do
@invocation_list[RDingus::InvocationRecord.new(:method, [:any])] = 'test'
@invocation_list[@invocation].should == "test"
end
it "should not store and retrieve for non equivalent invocations" do
@invocation_list[RDingus::InvocationRecord.new(:method2)] = "test"
@invocation_list[@invocation].should_not == "test"
end
describe "when redefining invocation" do
it "should use the last one" do
@invocation_list[@invocation] = "first"
@invocation_list[RDingus::InvocationRecord.new(:method, [:any])] = "second"
@invocation_list[@invocation].should == "second"
end
end
end
class Person
attr_accessor :name
attr_accessor :shoe_size
def eat(sandwich)
@sandwich = sandwich
@sandwich.cut :in => 2
@sandwich.take_a_bite
end
def drop(sandwich)
sandwich.drop
end
def opinion_of_sandwich
if @sandwich.cheese.spicy?
"yummy!"
else
"bland"
end
end
end
describe Person do
before :each do
@person = Person.new
end
describe "when specifying a shoe size" do
it "should save the shoe size" do
@person.shoe_size = 4
@person.shoe_size.should == 4
end
end
describe "when using a sandwich" do
before :each do
@sandwich = RDingus.new
@person.eat(@sandwich)
end
it "should be able to cut a sandwhich" do
@sandwich.calls.first.method.should == :cut
end
it "should cut into two slices" do
@sandwich.calls[0].args[0].should == {:in => 2}
end
it "should take a bite" do
@sandwich.should have_received(:take_a_bite)
end
it "should not have been dropped" do
@sandwich.should_not have_received(:dropped)
end
it "should raise an exception if it drops the sandwich" do
@sandwich.drop.raises "Dropped Sandwich!"
lambda { @person.drop(@sandwich) }.should raise_error("Dropped Sandwich!")
end
describe "that has spicy cheese" do
before :each do
@sandwich.cheese.spicy?.returns true
end
it "should have an opinion of 'yummy!'" do
@person.opinion_of_sandwich.should == "yummy!"
end
end
describe "that does not have spicy cheese" do
before :each do
@sandwich.cheese.spicy?.returns false
end
it "should have an opinion of 'bland'" do
@person.opinion_of_sandwich.should == "bland"
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment