Skip to content

Commit

Permalink
Merge pull request #421 from xaviershay/issue-392
Browse files Browse the repository at this point in the history
Verifying null objects only respond to defined methods.
  • Loading branch information
JonRowe committed Sep 25, 2013
2 parents 331b4b3 + 3f6328c commit 8e1a653
Show file tree
Hide file tree
Showing 5 changed files with 35 additions and 9 deletions.
2 changes: 2 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Enhancements:
* Yield the receiver to `any_instance` implementation blocks (Sam Phippen).
* Provide `instance_double` and `class_double` to create verifying doubles,
ported from `rspec-fire` (Xavier Shay).
* `as_null_object` on a verifying double only responds to defined methods
(Xavier Shay).
* Improved performance of double creation, particularly those with many
attributes. (Xavier Shay)
* Default value of `transfer_nested_constants` option for constant stubbing can
Expand Down
8 changes: 4 additions & 4 deletions lib/rspec/mocks/example_methods.rb
Original file line number Diff line number Diff line change
Expand Up @@ -35,28 +35,28 @@ def double(*args)
# @overload instance_double(doubled_class, stubs)
# @param doubled_class [String, Class]
# @param stubs [Hash] (optional) hash of message/return-value pairs
# @return InstanceVerifyingMock
# @return InstanceVerifyingDouble
#
# Constructs a test double against a specific class. If the given class
# name has been loaded, only instance methods defined on the class are
# allowed to be stubbed. In all other ways it behaves like a
# [double](double).
def instance_double(doubled_class, *args)
declare_verifying_double(InstanceVerifyingMock, doubled_class, *args)
declare_verifying_double(InstanceVerifyingDouble, doubled_class, *args)
end

# @overload class_double(doubled_class)
# @overload class_double(doubled_class, stubs)
# @param doubled_class [String, Module]
# @param stubs [Hash] (optional) hash of message/return-value pairs
# @return ClassVerifyingMock
# @return ClassVerifyingDouble
#
# Constructs a test double against a specific class. If the given class
# name has been loaded, only class methods defined on the class are
# allowed to be stubbed. In all other ways it behaves like a
# [double](double).
def class_double(doubled_class, *args)
declare_verifying_double(ClassVerifyingMock, doubled_class, *args)
declare_verifying_double(ClassVerifyingDouble, doubled_class, *args)
end

# Disables warning messages about expectations being set on nil.
Expand Down
18 changes: 15 additions & 3 deletions lib/rspec/mocks/verifying_double.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,22 @@
module RSpec
module Mocks

module VerifyingDouble
def method_missing(message, *args, &block)
# Null object conditional is an optimization. If not a null object,
# validity of method expectations will have been checked at definition
# time.
__mock_proxy.ensure_implemented(message) if null_object?
super
end
end

# A mock providing a custom proxy that can verify the validity of any
# method stubs or expectations against the public instance methods of the
# given class.
class InstanceVerifyingMock
class InstanceVerifyingDouble
include TestDouble
include VerifyingDouble

def initialize(doubled_module, *args)
@doubled_module = doubled_module
Expand All @@ -25,13 +36,14 @@ def __build_mock_proxy
end
end

# Similar to an InstanceVerifyingMock, except that it verifies against
# Similar to an InstanceVerifyingDouble, except that it verifies against
# public methods of the given class (i.e. the "class methods").
#
# Module needs to be in the inheritance chain for transferring nested
# constants to work.
class ClassVerifyingMock < Module
class ClassVerifyingDouble < Module
include TestDouble
include VerifyingDouble

def initialize(doubled_module, *args)
@doubled_module = doubled_module
Expand Down
2 changes: 0 additions & 2 deletions lib/rspec/mocks/verifying_proxy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,6 @@ def method_double
end
end

protected

def ensure_implemented(method_name)
@doubled_module.when_loaded do |original_module|
unless original_module.__send__(@method_checker, method_name)
Expand Down
14 changes: 14 additions & 0 deletions spec/rspec/mocks/verifying_double_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,13 @@ def prevents(&block)
o = instance_double(LoadedClass, :defined_instance_method => 1)
expect(o.defined_instance_method).to eq(1)
end

it 'only allows defined methods for null objects' do
o = instance_double('LoadedClass').as_null_object

expect(o.defined_instance_method).to eq(o)
prevents { o.undefined_method }
end
end
end

Expand Down Expand Up @@ -186,6 +193,13 @@ def prevents(&block)
expect(dbl1).to receive(:undefined_class_method)
}
end

it 'only allows defined methods for null objects' do
o = class_double('LoadedClass').as_null_object

expect(o.defined_class_method).to eq(o)
prevents { o.undefined_method }
end
end
end

Expand Down

0 comments on commit 8e1a653

Please sign in to comment.