Skip to content

Commit

Permalink
Merge branch 'simple_instance_of'
Browse files Browse the repository at this point in the history
  • Loading branch information
Brian Takita committed Aug 23, 2010
2 parents 61de96b + d52b9a6 commit ead8509
Show file tree
Hide file tree
Showing 18 changed files with 281 additions and 91 deletions.
12 changes: 12 additions & 0 deletions README.rdoc
Expand Up @@ -3,6 +3,18 @@
RR (Double Ruby) is a test double framework that features a rich
selection of double techniques and a terse syntax.

== Notes by Developer

There has been a major architectural issues and refactorings involved with introducing the all_instances_of feature.
Basically, I need to bind RR's dispaching to the class instead of the Eigenclass of each instance, just like Mocha does.
Unfortunately, it's not obviously feasible (without an ObjectSpace lookup) to support all of RR's methods (such as mocking).

Of course ObjectSpace is not readily supported in jRuby, since it causes general slowness in the intreperter.
I'm of the opinion that test speed is more important than having mocks on all instances of a class.

Thanks for your patience,
- Brian

== More Information
=== Mailing Lists
* double-ruby-users@rubyforge.org
Expand Down
1 change: 0 additions & 1 deletion doc/todo.txt
@@ -1 +0,0 @@
Remove all strategy classes and define a module with mock/stub/etc methods.
9 changes: 6 additions & 3 deletions lib/rr.rb
Expand Up @@ -29,8 +29,12 @@
require "#{dir}/rr/double_definitions/strategies/implementation/proxy"
require "#{dir}/rr/double_definitions/strategies/double_injection/double_injection_strategy"
require "#{dir}/rr/double_definitions/strategies/double_injection/instance"
require "#{dir}/rr/double_definitions/strategies/double_injection/instance_of_class"
require "#{dir}/rr/double_definitions/strategies/double_injection/any_instance_of_class"
require "#{dir}/rr/double_definitions/strategies/double_injection/any_instance_of"
require "#{dir}/rr/double_definitions/strategies/double_injection/new_instance_of"
require "#{dir}/rr/adapters/rr_methods"
require "#{dir}/rr/double_definitions/double_injections/instance"
require "#{dir}/rr/double_definitions/double_injections/any_instance_of"
require "#{dir}/rr/double_definitions/double_injections/new_instance_of"
require "#{dir}/rr/double_definitions/double_definition"

require "#{dir}/rr/injections/injection"
Expand All @@ -48,7 +52,6 @@
require "#{dir}/rr/double_definitions/double_definition_create_blank_slate"
require "#{dir}/rr/double_definitions/double_definition_create"
require "#{dir}/rr/double_definitions/child_double_definition_create"
require "#{dir}/rr/adapters/rr_methods"

require "#{dir}/rr/double"
require "#{dir}/rr/double_matches"
Expand Down
8 changes: 8 additions & 0 deletions lib/rr/adapters/rr_methods.rb
Expand Up @@ -130,6 +130,14 @@ def received(subject)
RR::SpyVerificationProxy.new(subject)
end

def new_instance_of(*args, &block)
RR::DoubleDefinitions::DoubleInjections::NewInstanceOf.call(*args, &block)
end

def any_instance_of(*args, &block)
RR::DoubleDefinitions::DoubleInjections::AnyInstanceOf.call(*args, &block)
end

instance_methods.each do |name|
alias_method "rr_#{name}", name
end
Expand Down
27 changes: 22 additions & 5 deletions lib/rr/double_definitions/double_definition_create.rb
@@ -1,19 +1,36 @@
module RR
module DoubleDefinitions
class DoubleDefinitionCreate # :nodoc
extend(Module.new do
def default_double_injection_strategy
@default_double_injection_strategy ||= lambda do |double_injection_create|
Strategies::DoubleInjection::Instance.new(double_injection_create)
end
end

def set_default_double_injection_strategy(strategy_lambda)
original_strategy_lambda = default_double_injection_strategy
begin
@default_double_injection_strategy = strategy_lambda
yield
ensure
@default_double_injection_strategy = original_strategy_lambda
end
end
end)

attr_reader :subject, :verification_strategy, :implementation_strategy, :double_injection_strategy
NO_SUBJECT = Object.new

include Space::Reader

def initialize
@verification_strategy = nil
@verification_strategy = Strategies::Verification::Stub.new(self)
@implementation_strategy = Strategies::Implementation::Reimplementation.new(self)
@double_injection_strategy = Strategies::DoubleInjection::Instance.new(self)
@double_injection_strategy = self.class.default_double_injection_strategy.call(self)
end

def call(method_name, *args, &handler)
raise DoubleDefinitionCreateError if no_subject?
definition = DoubleDefinition.new(self)
verification_strategy.call(definition, method_name, args, handler)
implementation_strategy.call(definition, method_name, args, handler)
Expand Down Expand Up @@ -113,11 +130,11 @@ def strong(subject=NO_SUBJECT, method_name=nil, &definition_eval_block)

# DoubleInjection Strategies
def any_instance_of(subject=NO_SUBJECT, method_name=nil, &definition_eval_block)
self.add_double_injection_strategy(::RR::DoubleDefinitions::Strategies::DoubleInjection::AnyInstanceOfClass, subject, method_name, &definition_eval_block)
self.add_double_injection_strategy(::RR::DoubleDefinitions::Strategies::DoubleInjection::AnyInstanceOf, subject, method_name, &definition_eval_block)
end

def instance_of(subject=NO_SUBJECT, method_name=nil, &definition_eval_block)
self.add_double_injection_strategy(::RR::DoubleDefinitions::Strategies::DoubleInjection::InstanceOfClass, subject, method_name, &definition_eval_block)
self.add_double_injection_strategy(::RR::DoubleDefinitions::Strategies::DoubleInjection::NewInstanceOf, subject, method_name, &definition_eval_block)
end
end
end
Expand Down
28 changes: 28 additions & 0 deletions lib/rr/double_definitions/double_injections/any_instance_of.rb
@@ -0,0 +1,28 @@
module RR
module DoubleDefinitions
module DoubleInjections
class AnyInstanceOf
extend(Module.new do
include RR::Adapters::RRMethods

def call(subject_class, stubbed_methods=nil, &block)
::RR::DoubleDefinitions::DoubleDefinitionCreate.set_default_double_injection_strategy(lambda do |double_definition_create|
::RR::DoubleDefinitions::Strategies::DoubleInjection::AnyInstanceOf.new(double_definition_create)
end) do
if stubbed_methods
subject_class.class_eval do
stubbed_methods.each do |name, value|
value_proc = value.is_a?(Proc) ? value : lambda {value}
RR.stub(subject_class, name).returns(&value_proc)
end
end
else
block.call(subject_class)
end
end
end
end)
end
end
end
end
16 changes: 16 additions & 0 deletions lib/rr/double_definitions/double_injections/instance.rb
@@ -0,0 +1,16 @@
module RR
module DoubleDefinitions
module DoubleInjections
class Instance
extend(Module.new do
include ::RR::Adapters::RRMethods

def call(double_method_name, *args, &definition_eval_block)
double_definition_create = DoubleDefinitions::DoubleDefinitionCreate.new
double_definition_create.send(double_method_name, *args, &definition_eval_block)
end
end)
end
end
end
end
50 changes: 50 additions & 0 deletions lib/rr/double_definitions/double_injections/new_instance_of.rb
@@ -0,0 +1,50 @@
module RR
module DoubleDefinitions
module DoubleInjections
class NewInstanceOf
extend(Module.new do
include RR::Adapters::RRMethods
def call(subject, stubbed_methods={})
double_definition_create = DoubleDefinitionCreate.new.stub
stub(subject).new do |*args| # TODO: Need to stub allocate instead of new
subject = subject.allocate
add_stubbed_methods(subject, stubbed_methods)
add_method_chain_definition(subject, double_definition_create)
yield(subject) if block_given?
initialize_subject_instance(subject, args)
end
DoubleDefinitionCreateBlankSlate.new(double_definition_create)
end

protected
def add_stubbed_methods(subject_instance, stubbed_methods)
stubbed_methods.each do |name, value|
value_proc = value.is_a?(Proc) ? value : lambda {value}
stub(subject_instance, name).returns(&value_proc)
end
end

def add_method_chain_definition(subject_instance, double_definition_create)
implementation_strategy = double_definition_create.implementation_strategy
if implementation_strategy.method_name
stub(subject_instance).method_missing(
implementation_strategy.method_name,
*implementation_strategy.args,
&implementation_strategy.handler
)
end
end

def initialize_subject_instance(subject_instance, args)
if args.last.is_a?(ProcFromBlock)
subject_instance.__send__(:initialize, *args[0..(args.length-2)], &args.last)
else
subject_instance.__send__(:initialize, *args)
end
subject_instance
end
end)
end
end
end
end
@@ -0,0 +1,31 @@
module RR
module DoubleDefinitions
module Strategies
module DoubleInjection
# This class is Deprecated.
# Calling instance_of will cause all instances of the passed in Class
# to have the Double defined.
#
# The following example mocks all User's valid? method and return false.
# mock.instance_of(User).valid? {false}
#
# The following example mocks and proxies User#projects and returns the
# first 3 projects.
# mock.instance_of(User).projects do |projects|
# projects[0..2]
# end
class AnyInstanceOf < DoubleInjectionStrategy
protected
def do_call
if !double_definition_create.no_subject? && !double_definition_create.subject.is_a?(Class)
raise ArgumentError, "instance_of only accepts class objects"
end
# subject, method_name
double_injection = Injections::DoubleInjection.find_or_create(subject, method_name)
Double.new(double_injection, definition)
end
end
end
end
end
end

This file was deleted.

Expand Up @@ -2,6 +2,7 @@ module RR
module DoubleDefinitions
module Strategies
module DoubleInjection
# This class is Deprecated.
# Calling instance_of will cause all instances of the passed in Class
# to have the Double defined.
#
Expand All @@ -13,21 +14,14 @@ module DoubleInjection
# mock.instance_of(User).projects do |projects|
# projects[0..2]
# end
class InstanceOfClass < DoubleInjectionStrategy
def initialize(*args)
super

class NewInstanceOf < DoubleInjectionStrategy
protected
def do_call
if !double_definition_create.no_subject? && !double_definition_create.subject.is_a?(Class)
raise ArgumentError, "instance_of only accepts class objects"
end
end

protected
def do_call
instance_of_subject_double_definition_create = DoubleDefinitionCreate.new
instance_of_subject_double_definition_create.stub(subject)
instance_of_subject_double_definition_create.call(:new) do |*args|
add_double_to_instance(subject.allocate, *args)
DoubleDefinitions::DoubleInjections::NewInstanceOf.call(subject) do |subject|
add_double_to_instance(subject, *args)
end
end

Expand Down
3 changes: 2 additions & 1 deletion lib/rr/injections/method_missing_injection.rb
Expand Up @@ -51,9 +51,10 @@ def reset

protected
def bind_method
subject_class_object_id = subject_class.object_id
subject_class.class_eval((<<-METHOD), __FILE__, __LINE__ + 1)
def method_missing(method_name, *args, &block)
MethodDispatches::MethodMissingDispatch.new(self, method_name, args, block).call
MethodDispatches::MethodMissingDispatch.new(self, ObjectSpace._id2ref(#{subject_class_object_id}), method_name, args, block).call
end
METHOD
end
Expand Down
15 changes: 7 additions & 8 deletions lib/rr/method_dispatches/method_missing_dispatch.rb
Expand Up @@ -7,15 +7,14 @@ def original_method_missing_alias_name
end
end)

attr_reader :subject, :method_name
def initialize(subject, method_name, args, block)
@subject, @method_name, @args, @block = subject, method_name, args, block
attr_reader :subject, :subject_class, :method_name
def initialize(subject, subject_class, method_name, args, block)
@subject, @subject_class, @method_name, @args, @block = subject, subject_class, method_name, args, block
end

def call
if Injections::DoubleInjection.exists_by_subject?(subject, method_name)
if Injections::DoubleInjection.exists?(subject_class, method_name)
@double = find_double_to_attempt

if double
call_yields
return_value = extract_subject_from_return_value(call_implementation)
Expand All @@ -33,7 +32,7 @@ def call
end

def call_original_method
Injections::DoubleInjection.find_or_create_by_subject(subject, method_name).dispatch_method_delegates_to_dispatch_original_method do
Injections::DoubleInjection.find_or_create(subject_class, method_name).dispatch_method_delegates_to_dispatch_original_method do
call_original_method_missing
end
end
Expand All @@ -45,7 +44,7 @@ def call_implementation
double.method_call(args)
call_original_method
else
if double_injection = Injections::DoubleInjection.find_by_subject(subject, method_name)
if double_injection = Injections::DoubleInjection.find(subject_class, method_name)
double_injection.bind_method
# The DoubleInjection takes care of calling double.method_call
subject.__send__(method_name, *args, &block)
Expand All @@ -56,7 +55,7 @@ def call_implementation
end

def double_injection
Injections::DoubleInjection.find_or_create_by_subject(subject, method_name)
Injections::DoubleInjection.find_or_create(subject_class, method_name)
end
end
end
Expand Down
2 changes: 1 addition & 1 deletion lib/rr/space.rb
Expand Up @@ -104,7 +104,7 @@ def reset_singleton_method_added_injections
end
Injections::SingletonMethodAddedInjection.instances.clear
end

def reset_recorded_calls
@recorded_calls.clear
end
Expand Down

0 comments on commit ead8509

Please sign in to comment.