not_a_mock 1.0.0

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,105 @@
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
+ case method
38
+ when Symbol
39
+ NotAMock::CallRecorder.instance.untrack_method(self, method)
40
+ NotAMock::Stubber.instance.unstub_method(self, method)
41
+ NotAMock::Stubber.instance.stub_method(self, method, &block)
42
+ NotAMock::CallRecorder.instance.track_method(self, method)
43
+ when Hash
44
+ stub_methods(method)
45
+ else
46
+ raise ArgumentError
47
+ end
48
+ end
49
+
50
+ # Takes a hash of method names mapped to results, and replaces each named
51
+ # method on this object with a stub version returning the corresponding result.
52
+ #
53
+ # Calls to stubbed methods are recorded in the NotAMock::CallRecorder,
54
+ # so you can later make assertions about them as described in
55
+ # NotAMock::Matchers.
56
+ def stub_methods(methods)
57
+ methods.each do |method, result|
58
+ stub_method(method) {|*args| result }
59
+ end
60
+ end
61
+
62
+ # Takes a hash of method names mapped to exceptions, and replaces each named
63
+ # method on this object with a stub version returning the corresponding exception.
64
+ def stub_methods_to_raise(methods)
65
+ methods.each do |method, exception|
66
+ stub_method(method) {|*args| raise exception }
67
+ end
68
+ end
69
+ alias_method(:stub_method_to_raise, :stub_methods_to_raise)
70
+
71
+ # Removes the stubbed versions of the given methods and restores the
72
+ # original methods.
73
+ def unstub_methods(*methods)
74
+ methods.each do |method, result|
75
+ NotAMock::CallRecorder.instance.untrack_method(self, method)
76
+ NotAMock::Stubber.instance.unstub_method(self, method)
77
+ end
78
+ end
79
+ alias_method(:unstub_method, :unstub_methods)
80
+
81
+ class << self
82
+ # Called on a class, creates a stub instance of that class. Takes a hash of
83
+ # method names and their returns values, and creates those methods on the new
84
+ # stub instance.
85
+ #
86
+ # See NotAMock::Stub for more details about the returned objects.
87
+ def stub_instance(methods = {})
88
+ NotAMock::Stub.new(self, methods)
89
+ end
90
+ end
91
+
92
+ # Returns the metaclass of this object. For an explanation of metaclasses, see:
93
+ # http://whytheluckystiff.net/articles/seeingMetaclassesClearly.html
94
+ def metaclass
95
+ class << self
96
+ self
97
+ end
98
+ end
99
+
100
+ # Evaluates the block in the context of this object's metaclass.
101
+ def meta_eval(&block)
102
+ metaclass.instance_eval(&block)
103
+ end
104
+
105
+ 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,69 @@
1
+ require 'singleton'
2
+ require 'not_a_mock/object_extensions'
3
+
4
+ module NotAMock
5
+ # The Stubber is a singleton that keeps track of all the stub methods
6
+ # installed in any object.
7
+ class Stubber
8
+ include Singleton
9
+
10
+ def initialize
11
+ @stubbed_methods = []
12
+ end
13
+
14
+ # Stub +method+ on +object+ to evalutate +block+ and return the result.
15
+ #
16
+ # You should call Object#stub_method rathing than calling this directly.
17
+ def stub_method(object, method, &block) #:nodoc:
18
+ unless @stubbed_methods.include?([object, method])
19
+ @stubbed_methods << [object, method]
20
+ add_hook(object, method, &block)
21
+ end
22
+ end
23
+
24
+ # Remove the stubbed +method+ on +object+.
25
+ #
26
+ # You should call Object#unstub_methods rather than calling this directly.
27
+ def unstub_method(object, method) #:nodoc:
28
+ if @stubbed_methods.delete([object, method])
29
+ remove_hook(object, method)
30
+ end
31
+ end
32
+
33
+ # Removes all stub methods.
34
+ def reset
35
+ @stubbed_methods.each do |object, method|
36
+ remove_hook(object, method)
37
+ end
38
+ @stubbed_methods = []
39
+ end
40
+
41
+ private
42
+
43
+ def add_hook(object, method, &block)
44
+ method_exists = method_at_any_level?(object, method.to_s)
45
+ object.meta_eval do
46
+ alias_method("__unstubbed_#{method}", method) if method_exists
47
+ define_method(method, &block)
48
+ end
49
+ end
50
+
51
+ def remove_hook(object, method)
52
+ method_exists = method_at_any_level?(object, "__unstubbed_#{method}")
53
+ object.meta_eval do
54
+ if method_exists
55
+ alias_method(method, "__unstubbed_#{method}")
56
+ remove_method("__unstubbed_#{method}")
57
+ else
58
+ remove_method(method)
59
+ end
60
+ end
61
+ end
62
+
63
+ def method_at_any_level?(object, method)
64
+ object.methods.include?(method) ||
65
+ object.protected_methods.include?(method) ||
66
+ object.private_methods.include?(method)
67
+ end
68
+ end
69
+ end
data/lib/not_a_mock.rb ADDED
@@ -0,0 +1,14 @@
1
+ require 'not_a_mock/active_record_extensions'
2
+ require 'not_a_mock/argument_constraint_extensions'
3
+ require 'not_a_mock/call_recorder'
4
+ require 'not_a_mock/matchers'
5
+ require 'not_a_mock/matchers/anything_matcher'
6
+ require 'not_a_mock/matchers/args_matcher'
7
+ require 'not_a_mock/matchers/call_matcher'
8
+ require 'not_a_mock/matchers/method_matcher'
9
+ require 'not_a_mock/matchers/result_matcher'
10
+ require 'not_a_mock/matchers/times_matcher'
11
+ require 'not_a_mock/object_extensions'
12
+ require 'not_a_mock/rspec_mock_framework_adapter'
13
+ require 'not_a_mock/stubber'
14
+ require 'not_a_mock/stub'
@@ -0,0 +1,68 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'not_a_mock'
3
+
4
+ class TrackedClass
5
+ def my_method(argument)
6
+ "result"
7
+ end
8
+ end
9
+
10
+ describe "A recorded method" do
11
+
12
+ before do
13
+ @recorder = NotAMock::CallRecorder.instance
14
+ @object = TrackedClass.new
15
+ @object.track_method(:my_method)
16
+ end
17
+
18
+ it "should record a method call" do
19
+ @object.my_method("argument")
20
+
21
+ @recorder.calls.should include(:object => @object, :method => :my_method, :args => ["argument"], :result => "result")
22
+ end
23
+
24
+ it "should not record calls after untrack_method" do
25
+ @object.my_method("argument 1")
26
+ @object.untrack_method(:my_method)
27
+ @object.my_method("argument 2")
28
+
29
+ @recorder.calls.should include(:object => @object, :method => :my_method, :args => ["argument 1"], :result => "result")
30
+ @recorder.calls.should_not include(:object => @object, :method => :my_method, :args => ["argument 2"], :result => "result")
31
+ end
32
+
33
+ it "should not record calls after stop_all_recording" do
34
+ @object.track_method(:my_method)
35
+ @object.my_method("argument 1")
36
+ @recorder.untrack_all
37
+ @object.my_method("argument 2")
38
+
39
+ @recorder.calls.should include(:object => @object, :method => :my_method, :args => ["argument 1"], :result => "result")
40
+ @recorder.calls.should_not include(:object => @object, :method => :my_method, :args => ["argument 2"], :result => "result")
41
+ end
42
+
43
+ it "should not break when track_method is called again" do
44
+ @object.track_method(:my_method)
45
+ @object.my_method("argument 1")
46
+ @object.untrack_method(:my_method)
47
+ @object.my_method("argument 2")
48
+
49
+ @recorder.calls.should include(:object => @object, :method => :my_method, :args => ["argument 1"], :result => "result")
50
+ @recorder.calls.should_not include(:object => @object, :method => :my_method, :args => ["argument 2"], :result => "result")
51
+ end
52
+
53
+ it "should not break when untrack_method is called more than once" do
54
+ @object.track_method(:my_method)
55
+ @object.my_method("argument 1")
56
+ @object.untrack_method(:my_method)
57
+ @object.untrack_method(:my_method)
58
+ @object.my_method("argument 2")
59
+
60
+ @recorder.calls.should include(:object => @object, :method => :my_method, :args => ["argument 1"], :result => "result")
61
+ @recorder.calls.should_not include(:object => @object, :method => :my_method, :args => ["argument 2"], :result => "result")
62
+ end
63
+
64
+ after do
65
+ @recorder.reset
66
+ end
67
+
68
+ end
@@ -0,0 +1,217 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'not_a_mock'
3
+
4
+ class TrackedClass < Object
5
+ def initialize(*calls)
6
+ calls.each do |call|
7
+ NotAMock::CallRecorder.instance.calls << call.merge(:object => self)
8
+ end
9
+ end
10
+
11
+ def inspect
12
+ "TrackedClass"
13
+ end
14
+ end
15
+
16
+ describe NotAMock::Matchers::AnythingMatcher do
17
+
18
+ it "should match if a method was called" do
19
+ @object = TrackedClass.new({ :method => :length, :args => [], :result => nil })
20
+ @matcher = have_been_called
21
+ @matcher.matches?(@object).should be_true
22
+ @matcher.negative_failure_message.should == "TrackedClass was called"
23
+ end
24
+
25
+ it "should not match if a method wasn't called" do
26
+ @object = TrackedClass.new
27
+ @matcher = have_been_called
28
+ @matcher.matches?(@object).should be_false
29
+ @matcher.failure_message.should == "TrackedClass was never called"
30
+ end
31
+
32
+ after do
33
+ NotAMock::CallRecorder.instance.reset
34
+ end
35
+
36
+ end
37
+
38
+ describe NotAMock::Matchers::MethodMatcher do
39
+
40
+ it "should match a called method" do
41
+ @object = TrackedClass.new({ :method => :length, :args => [], :result => nil })
42
+ @matcher = have_received(:length)
43
+ @matcher.matches?(@object).should be_true
44
+ @matcher.negative_failure_message.should == "TrackedClass received length"
45
+ end
46
+
47
+ it "should not match an uncalled method" do
48
+ @object = TrackedClass.new
49
+ @matcher = have_received(:width)
50
+ @matcher.matches?(@object).should be_false
51
+ @matcher.failure_message.should == "TrackedClass didn't receive width"
52
+ end
53
+
54
+ after do
55
+ NotAMock::CallRecorder.instance.reset
56
+ end
57
+
58
+ end
59
+
60
+ describe NotAMock::Matchers::ArgsMatcher, "matching calls with arguments " do
61
+
62
+ before do
63
+ @object = TrackedClass.new({ :method => :length, :args => [1, [2, 3, 4], 5], :result => nil })
64
+ end
65
+
66
+ it "should match a called method with the correct arguments" do
67
+ @matcher = have_received(:length).with(1, [2, 3, 4], 5)
68
+ @matcher.matches?(@object).should be_true
69
+ @matcher.negative_failure_message.should == "TrackedClass received length, with args [1, [2, 3, 4], 5]"
70
+ end
71
+
72
+ it "should not match a called method with the wrong arguments" do
73
+ @matcher = have_received(:length).with(3, 2, 1)
74
+ @matcher.matches?(@object).should be_false
75
+ @matcher.failure_message.should == "TrackedClass received length, but not with args [3, 2, 1]"
76
+ end
77
+
78
+ it "should match a called method with a wildcard argument" do
79
+ @matcher = have_received(:length).with(1, anything, 5)
80
+ @matcher.matches?(@object).should be_true
81
+ @matcher.matches?(@object).should be_true
82
+ @matcher.negative_failure_message.should == "TrackedClass received length, with args [1, anything, 5]"
83
+ end
84
+
85
+ it "should match a called method with a wildcard argument" do
86
+ @matcher = have_received(:length).with(1, in_any_order([4, 3, 2]), 5)
87
+ @matcher.matches?(@object).should be_true
88
+ @matcher.matches?(@object).should be_true
89
+ @matcher.negative_failure_message.should == "TrackedClass received length, with args [1, in_any_order([4, 3, 2]), 5]"
90
+ end
91
+
92
+ after do
93
+ NotAMock::CallRecorder.instance.reset
94
+ end
95
+
96
+ end
97
+
98
+ describe NotAMock::Matchers::ArgsMatcher, "matching calls without arguments" do
99
+
100
+ before do
101
+ @object = TrackedClass.new(
102
+ { :method => :length, :args => [1, 2, 3], :result => nil },
103
+ { :method => :width, :args => [], :result => nil }
104
+ )
105
+ end
106
+
107
+ it "should match a method called without arguments" do
108
+ @matcher = have_received(:width).without_args
109
+ @matcher.matches?(@object).should be_true
110
+ @matcher.negative_failure_message.should == "TrackedClass received width, without args"
111
+ end
112
+
113
+ it "should not match a method called with arguments" do
114
+ @matcher = have_received(:length).without_args
115
+ @matcher.matches?(@object).should be_false
116
+ @matcher.failure_message.should == "TrackedClass received length, but not without args"
117
+ end
118
+
119
+ after do
120
+ NotAMock::CallRecorder.instance.reset
121
+ end
122
+
123
+ end
124
+
125
+ describe NotAMock::Matchers::ResultMatcher do
126
+
127
+ before do
128
+ @object = TrackedClass.new(
129
+ { :method => :length, :args => [], :result => 13 },
130
+ { :method => :width, :args => [], :result => 42 }
131
+ )
132
+ end
133
+
134
+ it "should match a method that returned the correct result" do
135
+ @matcher = have_received(:length).and_returned(13)
136
+ @matcher.matches?(@object).should be_true
137
+ @matcher.negative_failure_message.should == "TrackedClass received length, and returned 13"
138
+ end
139
+
140
+ it "should not match a method the returned the wrong result" do
141
+ @matcher = have_received(:width).and_returned(13)
142
+ @matcher.matches?(@object).should be_false
143
+ @matcher.failure_message.should == "TrackedClass received width, but didn't return 13"
144
+ end
145
+
146
+ after do
147
+ NotAMock::CallRecorder.instance.reset
148
+ end
149
+
150
+ end
151
+
152
+ describe NotAMock::Matchers::TimesMatcher do
153
+
154
+ before do
155
+ @object = TrackedClass.new(
156
+ { :method => :once, :args => [], :result => nil },
157
+ { :method => :twice, :args => [], :result => nil },
158
+ { :method => :twice, :args => [], :result => nil },
159
+ { :method => :thrice, :args => [], :result => nil },
160
+ { :method => :thrice, :args => [], :result => nil },
161
+ { :method => :thrice, :args => [], :result => nil }
162
+ )
163
+ end
164
+
165
+ it "should match a method that was called once" do
166
+ @matcher = have_received(:once).once
167
+ @matcher.matches?(@object).should be_true
168
+ @matcher.negative_failure_message.should == "TrackedClass received once, once"
169
+ end
170
+
171
+ it "should match a method that was called twice" do
172
+ @matcher = have_received(:twice).twice
173
+ @matcher.matches?(@object).should be_true
174
+ @matcher.negative_failure_message.should == "TrackedClass received twice, twice"
175
+ end
176
+
177
+ it "should match a method that was called 3 times" do
178
+ @matcher = have_received(:thrice).exactly(3).times
179
+ @matcher.matches?(@object).should be_true
180
+ @matcher.negative_failure_message.should == "TrackedClass received thrice, 3 times"
181
+ end
182
+
183
+ it "should not match a method a method that was called the wrong number of times" do
184
+ @matcher = have_received(:thrice).once
185
+ @matcher.matches?(@object).should be_false
186
+ @matcher.failure_message.should == "TrackedClass received thrice, but 3 times"
187
+ end
188
+
189
+ after do
190
+ NotAMock::CallRecorder.instance.reset
191
+ end
192
+
193
+ end
194
+
195
+ describe "A chain of matchers" do
196
+
197
+ before do
198
+ @object = TrackedClass.new({ :method => :length, :args => [1, 2, 3], :result => 42 })
199
+ end
200
+
201
+ it "should match the correct method, args, and result" do
202
+ @matcher = have_received(:length).with(1, 2, 3).and_returned(42)
203
+ @matcher.matches?(@object).should be_true
204
+ @matcher.negative_failure_message.should == "TrackedClass received length, with args [1, 2, 3], and returned 42"
205
+ end
206
+
207
+ it "should not match the correct method, but with the incorrect args" do
208
+ @matcher = have_received(:length).with(3, 2, 1).and_returned(42)
209
+ @matcher.matches?(@object).should be_false
210
+ @matcher.failure_message.should == "TrackedClass received length, but not with args [3, 2, 1]"
211
+ end
212
+
213
+ after do
214
+ NotAMock::CallRecorder.instance.reset
215
+ end
216
+
217
+ end
@@ -0,0 +1,29 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'not_a_mock'
3
+
4
+ class ExampleActiveRecord < ActiveRecord::Base
5
+ end
6
+
7
+ describe "A stubbed ActiveRecord object" do
8
+
9
+ before do
10
+ @example = ExampleActiveRecord.stub_instance
11
+ end
12
+
13
+ it "should return a valid id" do
14
+ lambda { @example.id }.should_not raise_error(NoMethodError)
15
+ @example.id.should be_an_instance_of(Fixnum)
16
+ end
17
+
18
+ it "should return the id as a string for to_param" do
19
+ lambda { @example.to_param }.should_not raise_error(NoMethodError)
20
+ @example.to_param.should be_an_instance_of(String)
21
+ @example.to_param.should == @example.id.to_s
22
+ end
23
+
24
+ after do
25
+ NotAMock::CallRecorder.instance.reset
26
+ NotAMock::Stubber.instance.reset
27
+ end
28
+
29
+ end
@@ -0,0 +1,49 @@
1
+ $LOAD_PATH.unshift File.dirname(__FILE__) + '/../lib'
2
+ require 'not_a_mock'
3
+
4
+ describe "A stub instance" do
5
+
6
+ before do
7
+ @object = String.stub_instance(:length => 42, :id => 99)
8
+ end
9
+
10
+ it "should return the right result for a stubbed method" do
11
+ @object.length.should == 42
12
+ end
13
+
14
+ it "should return its name when inspected" do
15
+ @object.inspect.should == "Stub String"
16
+ end
17
+
18
+ it "should handle the id method being stubbed" do
19
+ @object.id.should == 99
20
+ end
21
+
22
+ it "should raise an error when a method that's not stubbed is called" do
23
+ lambda { @object.whatever }.should raise_error(NoMethodError)
24
+ end
25
+
26
+ it "should record a call to a stubbed method" do
27
+ @object.length
28
+ NotAMock::CallRecorder.instance.calls.should include(:object => @object, :method => :length, :args => [], :result => 42)
29
+ end
30
+
31
+ it "should allow adding of new stubbed methods" do
32
+ @object.stub_method(:width => 7)
33
+ @object.width.should == 7
34
+ end
35
+
36
+ it "should identify itself as the underlying object" do
37
+ @object.is_a?(String).should be_true
38
+ @object.should be_kind_of(String)
39
+ @object.should be_kind_of(Object)
40
+ @object.should be_instance_of(String)
41
+ @object.class.should == String
42
+ end
43
+
44
+ after do
45
+ NotAMock::CallRecorder.instance.reset
46
+ NotAMock::Stubber.instance.reset
47
+ end
48
+
49
+ end