derickbailey-notamock 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,28 @@
1
+ require 'not_a_mock/matchers/call_matcher'
2
+
3
+ module NotAMock
4
+ module Matchers
5
+ # Matcher for
6
+ # object.should have_been_called
7
+ class AnythingMatcher < CallMatcher
8
+
9
+ def initialize(parent = nil)
10
+ super parent
11
+ end
12
+
13
+ def matches_without_parents?
14
+ @calls = CallRecorder.instance.calls_by_object(@object)
15
+ !@calls.empty?
16
+ end
17
+
18
+ def failure_message_without_parents
19
+ if matched?
20
+ " was called"
21
+ else
22
+ " was never called"
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,74 @@
1
+ require 'not_a_mock/matchers/call_matcher'
2
+
3
+ module NotAMock
4
+ module Matchers
5
+ # Matcher for
6
+ # with(...)
7
+ #
8
+ # == Argument Matchers
9
+ #
10
+ # Not A Mock supports the use of RSpec's patterns for argument matching
11
+ # in mocks, and extends them. The most useful are listed below.
12
+ #
13
+ # === Anything Matcher
14
+ #
15
+ # The +anything+ pattern will match any value. For example:
16
+ #
17
+ # object.should have_received(:message).with(1, anything, 3)
18
+ #
19
+ # will match the following calls:
20
+ #
21
+ # object.message(1, 2, 3)
22
+ # object.message(1, 'Boo!', 3)
23
+ #
24
+ # but not:
25
+ #
26
+ # object.message(3, 2, 1)
27
+ # object.message(1, 2, 3, 4)
28
+ #
29
+ # === In Any Order Matcher
30
+ #
31
+ # The +in_any_order+ pattern will match an array argument, but won't care
32
+ # about order of elements in the array. For example:
33
+ #
34
+ # object.should have_received(:message).with(in_any_order([3, 2, 1]))
35
+ #
36
+ # will match the following calls:
37
+ #
38
+ # object.message([3, 2, 1])
39
+ # object.message([1, 2, 3])
40
+ #
41
+ # but not:
42
+ #
43
+ # object.message([1, 2, 3, 4])
44
+ class ArgsMatcher < CallMatcher
45
+
46
+ def initialize(args, parent = nil)
47
+ super parent
48
+ @args = args
49
+ end
50
+
51
+ def matches_without_parents?
52
+ @calls = @parent.calls.select {|entry| @args == entry[:args] }
53
+ !@calls.empty?
54
+ end
55
+
56
+ def failure_message_without_parents
57
+ if matched?
58
+ if @args.empty?
59
+ ", without args"
60
+ else
61
+ ", with args #{@args.inspect}"
62
+ end
63
+ else
64
+ if @args.empty?
65
+ ", but not without args"
66
+ else
67
+ ", but not with args #{@args.inspect}"
68
+ end
69
+ end
70
+ end
71
+
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,64 @@
1
+ module NotAMock
2
+ module Matchers
3
+ class CallMatcher
4
+
5
+ def initialize(parent = nil)
6
+ @parent = parent
7
+ end
8
+
9
+ def matches?(object)
10
+ @object = object
11
+ @matched = parent_matches? && matches_without_parents?
12
+ end
13
+
14
+ def matched?; @matched end
15
+
16
+ attr_reader :calls
17
+
18
+ def failure_message
19
+ if parent_matched?
20
+ parent_failure_message + failure_message_without_parents
21
+ else
22
+ parent_failure_message
23
+ end
24
+ end
25
+
26
+ def negative_failure_message
27
+ failure_message
28
+ end
29
+
30
+ def with(*args)
31
+ ArgsMatcher.new(args, self)
32
+ end
33
+
34
+ def without_args
35
+ ArgsMatcher.new([], self)
36
+ end
37
+
38
+ def and_returned(result)
39
+ ResultMatcher.new(result, self)
40
+ end
41
+
42
+ def exactly(n)
43
+ TimesMatcher.new(n, self)
44
+ end
45
+ def once; exactly(1) end
46
+ def twice; exactly(2) end
47
+
48
+ protected
49
+
50
+ def parent_matches?
51
+ @parent.nil? || @parent.matches?(@object)
52
+ end
53
+
54
+ def parent_matched?
55
+ @parent.nil? || @parent.matched?
56
+ end
57
+
58
+ def parent_failure_message
59
+ @parent ? @parent.failure_message : @object.inspect
60
+ end
61
+
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,29 @@
1
+ require 'not_a_mock/matchers/call_matcher'
2
+
3
+ module NotAMock
4
+ module Matchers
5
+ # Matcher for
6
+ # object.should have_received(...)
7
+ class MethodMatcher < CallMatcher
8
+
9
+ def initialize(method, parent = nil)
10
+ super parent
11
+ @method = method
12
+ end
13
+
14
+ def matches_without_parents?
15
+ @calls = CallRecorder.instance.calls_by_object_and_method(@object, @method)
16
+ !@calls.empty?
17
+ end
18
+
19
+ def failure_message_without_parents
20
+ if matched?
21
+ " received #{@method}"
22
+ else
23
+ " didn't receive #{@method}"
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,29 @@
1
+ require 'not_a_mock/matchers/call_matcher'
2
+
3
+ module NotAMock
4
+ module Matchers
5
+ # Matcher for
6
+ # and_returned(...)
7
+ class ResultMatcher < CallMatcher
8
+
9
+ def initialize(result, parent = nil)
10
+ super parent
11
+ @result = result
12
+ end
13
+
14
+ def matches_without_parents?
15
+ @calls = @parent.calls.select {|entry| entry[:result] == @result }
16
+ !@calls.empty?
17
+ end
18
+
19
+ def failure_message_without_parents
20
+ if matched?
21
+ ", and returned #{@result.inspect}"
22
+ else
23
+ ", but didn't return #{@result.inspect}"
24
+ end
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,46 @@
1
+ require 'not_a_mock/matchers/call_matcher'
2
+
3
+ module NotAMock
4
+ module Matchers
5
+ # Matcher for +once+, +twice+, and
6
+ # exactly(n).times
7
+ class TimesMatcher < CallMatcher
8
+
9
+ def initialize(times, parent = nil)
10
+ super parent
11
+ @times = times
12
+ end
13
+
14
+ def matches_without_parents?
15
+ @calls = @parent.calls
16
+ @calls.length == @times
17
+ end
18
+
19
+ def failure_message_without_parents
20
+ if matched?
21
+ ", #{times_in_english(@parent.calls.length)}"
22
+ else
23
+ ", but #{times_in_english(@parent.calls.length, true)}"
24
+ end
25
+ end
26
+
27
+ def times
28
+ self
29
+ end
30
+
31
+ private
32
+
33
+ def times_in_english(times, only = false)
34
+ case times
35
+ when 1
36
+ only ? "only once" : "once"
37
+ when 2
38
+ "twice"
39
+ else
40
+ "#{times} times"
41
+ end
42
+ end
43
+
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,107 @@
1
+ class Object
2
+
3
+ # Call this on any object or class with a list of method names. Any future
4
+ # calls to those methods will be recorded in NotAMock::CallRecorder.
5
+ #
6
+ # See NotAMock::Matchers for info on how to test which methods have been
7
+ # called, with what arguments, etc.
8
+ def track_methods(*methods)
9
+ if methods.empty?
10
+ self.public_methods(false).map {|method_name| methods << method_name.to_s.to_sym}
11
+ end
12
+
13
+ methods.each do |method|
14
+ NotAMock::CallRecorder.instance.track_method(self, method)
15
+ end
16
+ end
17
+ alias_method(:track_method, :track_methods)
18
+ alias_method(:log_calls_to, :track_methods) # For backwards compatibility.
19
+
20
+ # Stop recording calls for the given methods.
21
+ def untrack_methods(*methods)
22
+ methods.each do |method|
23
+ NotAMock::CallRecorder.instance.untrack_method(self, method)
24
+ end
25
+ end
26
+ alias_method(:untrack_method, :untrack_methods)
27
+
28
+ # If passed a symbol and a block, this replaces the named method on this
29
+ # object with a stub version that evaluates the block and returns the result.
30
+ #
31
+ # If passed a hash, this is an alias for stub_methods.
32
+ #
33
+ # Calls to stubbed methods are recorded in the NotAMock::CallRecorder,
34
+ # so you can later make assertions about them as described in
35
+ # NotAMock::Matchers.
36
+ def stub_method(method, &block)
37
+ obj = self
38
+ case method
39
+ when Symbol
40
+ NotAMock::CallRecorder.instance.untrack_method(obj, method)
41
+ NotAMock::Stubber.instance.unstub_method(obj, method)
42
+ stubber = NotAMock::Stubber.instance.stub_method(obj, method, &block)
43
+ NotAMock::CallRecorder.instance.track_method(obj, method)
44
+ when Hash
45
+ stub_methods(method)
46
+ else
47
+ raise ArgumentError
48
+ end
49
+ stubber
50
+ end
51
+
52
+ # Takes a hash of method names mapped to results, and replaces each named
53
+ # method on this object with a stub version returning the corresponding result.
54
+ #
55
+ # Calls to stubbed methods are recorded in the NotAMock::CallRecorder,
56
+ # so you can later make assertions about them as described in
57
+ # NotAMock::Matchers.
58
+ def stub_methods(methods)
59
+ methods.each do |method, result|
60
+ stub_method(method) {|*args| result }
61
+ end
62
+ end
63
+
64
+ # Takes a hash of method names mapped to exceptions, and replaces each named
65
+ # method on this object with a stub version returning the corresponding exception.
66
+ def stub_methods_to_raise(methods)
67
+ methods.each do |method, exception|
68
+ stub_method(method) {|*args| raise exception }
69
+ end
70
+ end
71
+ alias_method(:stub_method_to_raise, :stub_methods_to_raise)
72
+
73
+ # Removes the stubbed versions of the given methods and restores the
74
+ # original methods.
75
+ def unstub_methods(*methods)
76
+ methods.each do |method, result|
77
+ NotAMock::CallRecorder.instance.untrack_method(self, method)
78
+ NotAMock::Stubber.instance.unstub_method(self, method)
79
+ end
80
+ end
81
+ alias_method(:unstub_method, :unstub_methods)
82
+
83
+ class << self
84
+ # Called on a class, creates a stub instance of that class. Takes a hash of
85
+ # method names and their returns values, and creates those methods on the new
86
+ # stub instance.
87
+ #
88
+ # See NotAMock::Stub for more details about the returned objects.
89
+ def stub_instance(methods = {})
90
+ NotAMock::Stub.new(self, methods)
91
+ end
92
+ end
93
+
94
+ # Returns the metaclass of this object. For an explanation of metaclasses, see:
95
+ # http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
96
+ def metaclass
97
+ class << self
98
+ self
99
+ end
100
+ end
101
+
102
+ # Evaluates the block in the context of this object's metaclass.
103
+ def meta_eval(&block)
104
+ metaclass.instance_eval(&block)
105
+ end
106
+
107
+ end
@@ -0,0 +1,16 @@
1
+ module NotAMock
2
+ module RspecMockFrameworkAdapter
3
+
4
+ def setup_mocks_for_rspec
5
+ end
6
+
7
+ def verify_mocks_for_rspec
8
+ end
9
+
10
+ def teardown_mocks_for_rspec
11
+ NotAMock::CallRecorder.instance.reset
12
+ NotAMock::Stubber.instance.reset
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,40 @@
1
+ module NotAMock
2
+ # Instances returned by Object.stub_instance are NotAMock::Stub objects.
3
+ # These do their best to masquerade as the real thing.
4
+ class Stub
5
+
6
+ # This is normall only called from Object.stub_instance.
7
+ def initialize(stubbed_class, methods = {}) #:nodoc:
8
+ @stubbed_class = stubbed_class
9
+ methods.each do |method, result|
10
+ self.meta_eval do
11
+ define_method(method) { result }
12
+ end
13
+ track_method(method)
14
+ end
15
+ end
16
+
17
+ # Returns "Stub StubbedClass".
18
+ def inspect
19
+ "Stub #{@stubbed_class.to_s}"
20
+ end
21
+
22
+ # Returns true if the class of the stubbed object or one of its superclasses is klass.
23
+ def is_a?(klass)
24
+ @stubbed_class.ancestors.include?(klass)
25
+ end
26
+
27
+ alias_method :kind_of?, :is_a?
28
+
29
+ # Returns true if the class of the stubbed object is klass.
30
+ def instance_of?(klass)
31
+ @stubbed_class == klass
32
+ end
33
+
34
+ # Returns the class of the stubbed object.
35
+ def class
36
+ @stubbed_class
37
+ end
38
+
39
+ end
40
+ end
@@ -0,0 +1,85 @@
1
+ require 'singleton'
2
+ require 'not_a_mock/object_extensions'
3
+
4
+ module NotAMock
5
+
6
+ # The Stubber is a singleton that keeps track of all the stub methods
7
+ # installed in any object.
8
+ class Stubber
9
+ include Singleton
10
+
11
+ def initialize
12
+ @stubbed_methods = {}
13
+ end
14
+
15
+ # Stub +method+ on +object+ to evalutate +block+ and return the result.
16
+ #
17
+ # You should call Object#stub_method rathing than calling this directly.
18
+ def stub_method(object, method, &block) #:nodoc:
19
+ unless @stubbed_methods.include?([object, method])
20
+ stubmethod = NotAMock::StubMethod.new(&block)
21
+ @stubbed_methods[[object, method]] = stubmethod
22
+ add_hook(object, method)
23
+ end
24
+ stubmethod
25
+ end
26
+
27
+ # Remove the stubbed +method+ on +object+.
28
+ #
29
+ # You should call Object#unstub_methods rather than calling this directly.
30
+ def unstub_method(object, method) #:nodoc:
31
+ if @stubbed_methods.delete([object, method])
32
+ remove_hook(object, method)
33
+ end
34
+ end
35
+
36
+ # Removes all stub methods.
37
+ def reset
38
+ @stubbed_methods.each do |key, value|
39
+ object = key[0]
40
+ method = key[1]
41
+ remove_hook(object, method)
42
+ end
43
+ @stubbed_methods = {}
44
+ end
45
+
46
+ #Retrieve the stub method data and code for stubbed +method+ on the +object+
47
+ def get_stubmethod(object, method)
48
+ @stubbed_methods[[object, method]]
49
+ end
50
+
51
+ private
52
+
53
+ def add_hook(object, method)
54
+ method_exists = method_at_any_level?(object, method.to_s)
55
+ object.meta_eval do
56
+ alias_method("__unstubbed_#{method}", method) if method_exists
57
+ end
58
+ object.instance_eval(<<-EOF, __FILE__, __LINE__)
59
+ def #{method}(*args, &block)
60
+ stubmethod = Stubber.instance.get_stubmethod(self, :#{method})
61
+ stubmethod.yield_to_block(&block)
62
+ stubmethod.execute_return_block(*args)
63
+ end
64
+ EOF
65
+ end
66
+
67
+ def remove_hook(object, method)
68
+ method_exists = method_at_any_level?(object, "__unstubbed_#{method}")
69
+ object.meta_eval do
70
+ if method_exists
71
+ alias_method(method, "__unstubbed_#{method}")
72
+ remove_method("__unstubbed_#{method}")
73
+ else
74
+ remove_method(method)
75
+ end
76
+ end
77
+ end
78
+
79
+ def method_at_any_level?(object, method)
80
+ object.methods.include?(method) ||
81
+ object.protected_methods.include?(method) ||
82
+ object.private_methods.include?(method)
83
+ end
84
+ end
85
+ end