caricature 0.7.6 → 0.7.7

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.
Files changed (39) hide show
  1. data/caricature.gemspec +3 -3
  2. data/lib/bin/Workarounds.dll.mdb +0 -0
  3. data/lib/caricature.rb +25 -25
  4. data/lib/caricature/clr.rb +6 -6
  5. data/lib/caricature/clr/aspnet_mvc.rb +54 -54
  6. data/lib/caricature/core_ext.rb +10 -10
  7. data/lib/caricature/core_ext/array.rb +9 -9
  8. data/lib/caricature/core_ext/class.rb +31 -14
  9. data/lib/caricature/core_ext/hash.rb +12 -12
  10. data/lib/caricature/core_ext/module.rb +14 -14
  11. data/lib/caricature/core_ext/object.rb +76 -18
  12. data/lib/caricature/core_ext/string.rb +16 -16
  13. data/lib/caricature/core_ext/system/string.rb +20 -20
  14. data/lib/caricature/core_ext/system/type.rb +26 -26
  15. data/lib/caricature/descriptor.rb +73 -73
  16. data/lib/caricature/expectation.rb +264 -263
  17. data/lib/caricature/isolation.rb +143 -143
  18. data/lib/caricature/isolator.rb +302 -302
  19. data/lib/caricature/messenger.rb +67 -67
  20. data/lib/caricature/method_call_recorder.rb +228 -228
  21. data/lib/caricature/verification.rb +60 -60
  22. data/lib/caricature/version.rb +1 -1
  23. data/spec/bacon/integration/clr_to_clr_spec.rb +4 -4
  24. data/spec/bacon/integration/clr_to_ruby_spec.rb +227 -227
  25. data/spec/bacon/integration/event_spec.rb +2 -2
  26. data/spec/bacon/integration/ruby_to_ruby_spec.rb +270 -270
  27. data/spec/bacon/integration/syntax_spec.rb +43 -0
  28. data/spec/bacon/unit/core_ext_spec.rb +87 -87
  29. data/spec/bacon/unit/expectation_spec.rb +300 -300
  30. data/spec/bacon/unit/interop_spec.rb +29 -29
  31. data/spec/bacon/unit/isolation_spec.rb +86 -86
  32. data/spec/bacon/unit/isolator_spec.rb +219 -219
  33. data/spec/bacon/unit/messaging_spec.rb +310 -310
  34. data/spec/bacon/unit/method_call_spec.rb +342 -342
  35. data/spec/bin/ClrModels.dll.mdb +0 -0
  36. data/spec/rspec/unit/event_spec.rb +1 -1
  37. metadata +31 -11
  38. data/spec/models.notused/ClrModels.cs +0 -241
  39. data/spec/models.notused/ruby_models.rb +0 -151
@@ -1,144 +1,144 @@
1
- require File.dirname(__FILE__) + '/isolator'
2
- require File.dirname(__FILE__) + '/method_call_recorder'
3
- require File.dirname(__FILE__) + '/expectation'
4
- require File.dirname(__FILE__) + '/verification'
5
-
6
- # Caricature - Bringing simple mocking to the DLR
7
- # ===============================================
8
- #
9
- # This project aims to make interop between IronRuby objects and .NET objects easier.
10
- # The idea is that it integrates nicely with bacon and later rspec and that it transparently lets you mock ironruby ojbects
11
- # as well as CLR objects/interfaces.
12
- # Caricature handles interfaces, interface inheritance, CLR objects, CLR object instances, Ruby classes and instances of Ruby classes.
13
- #
14
- # From the start I wanted to do away with names like mock, stub, record, replay, verify etc.
15
- # Instead I took the advice from Roy Osherhove and went with a name of Isolation for creating a mock.
16
- #
17
- # An Isolation will create what in Rhino.Mocks would be called a DynamicMock (but can be a partial too) :)
18
- # In Moq it would be the Loose mocking strategy.
19
- #
20
- # The naming of the methods for creating mocks is the one that JP Boodhoo proposed WhenToldTo and WasToldTo.
21
- # To specify a stub/expectation on an isolation you have one and only one way of doing that with the method called when_receiving.
22
- # Then only if you're interested in asserting if a method has been called you can use the did_receive? method for this.
23
- #
24
- #
25
- # isolation = Isolation.for(Ninja)
26
- # isolation.when_receiving(:attack) do |exp|
27
- # exp.with(:shuriken)
28
- # exp.return(3)
29
- # end
30
- #
31
- # Battle.new(mock)
32
- # battle.combat
33
- #
34
- # isolation.did_receive?(:attack).should.be.true?
35
- #
36
- #
37
- # It may be very important to note that when you're going to be isolating CLR classes to be used in other CLR classes
38
- # you still need to obide by the CLR rules. That means if you want to redefine a method you'll need to make sure it's
39
- # marked as virtual. Static types are still governed by static type rules. I'm working on a solution around those
40
- # problems but for the time being this is how it has to be.
41
- module Caricature
42
-
43
- # The context for an isolator. This contains the +subject+, +recorder+, +expectations+ and +messenger+
44
- IsolatorContext = Struct.new(:subject, :recorder, :expectations, :messenger)
45
-
46
- # Instead of using confusing terms like Mocking and Stubbing which is basically the same. Caricature tries
47
- # to unify those concepts by using a term of *Isolation*.
48
- # When you're testing you typically want to be in control of what you're testing. To do that you isolate
49
- # dependencies and possibly define return values for methods on them.
50
- # At a later stage you might be interested in which method was called, maybe even with which parameters
51
- # or you might be interested in the amount of times it has been called.
52
- class Isolation
53
-
54
- # the real instance of the isolated subject
55
- # used to forward calls in partial mocks
56
- attr_accessor :instance
57
-
58
- # the method call recorder
59
- attr_reader :recorder
60
-
61
- # the expecations set for this isolation
62
- attr_reader :expectations
63
-
64
- # Initializes a new instance of this isolation.
65
- def initialize(isolator, context)
66
- @instance = isolator.subject
67
- @messenger = context.messenger
68
- @messenger.recorder = @recorder = context.recorder
69
- @expectations = context.expectations
70
- @proxy = isolator.isolation
71
- isolator.isolation.class.instance_variable_set("@___context___", self)
72
- end
73
-
74
- # record and send the message to the isolation.
75
- # takes care of following expectations rules when sending messages.
76
- def send_message(method_name, return_type, *args, &b)
77
- @messenger.deliver(method_name, return_type, *args, &b)
78
- end
79
-
80
- # record and send the message to the isolation.
81
- # takes care of following expectations rules when sending messages.
82
- def send_class_message(method_name, return_type, *args, &b)
83
- @messenger.deliver_to_class(method_name, return_type, *args, &b)
84
- end
85
-
86
- # builds up an expectation for an instance method, allows for overriding the result returned by the method
87
- def create_override(method_name, &block)
88
- internal_create_override method_name, :instance, &block
89
- end
90
-
91
- # builds up an expectation for a class method, allows for overriding the result returned by the class method
92
- def create_class_override(method_name, &block)
93
- internal_create_override method_name, :class, &block
94
- end
95
-
96
- # asserts whether the method has been called for the specified configuration
97
- def verify(method_name, &block)
98
- internal_verify method_name, :instance, &block
99
- end
100
-
101
- # asserts whether the method has been called for the specified configuration
102
- def class_verify(method_name, &block)
103
- internal_verify method_name, :class, &block
104
- end
105
-
106
- class << self
107
-
108
- # Creates an isolation object complete with proxy and method call recorder
109
- # It works out which isolation it needs to create and provide and initializes the
110
- # method call recorder
111
- def for(subject, recorder = MethodCallRecorder.new, expectations = Expectations.new)
112
- context = IsolatorContext.new subject, recorder, expectations
113
-
114
- isolator = RubyIsolator.for context
115
- isolation = new(isolator, context)
116
- isolator.isolation
117
- end
118
-
119
- end
120
-
121
- protected
122
-
123
- def internal_create_override(method_name, mode=:instance, &block)
124
- builder = ExpectationBuilder.new method_name
125
- block.call builder unless block.nil?
126
- exp = builder.build
127
- expectations.add_expectation exp, mode
128
- exp
129
- end
130
-
131
- def internal_verify(method_name, mode=:instance, &block)
132
- verification = Verification.new(method_name, recorder, mode)
133
- block.call verification unless block.nil?
134
- verification
135
- end
136
- end
137
-
138
- # +Mock+ is a synonym for +Isolation+ so you can still use it if you're that way inclined
139
- Mock = Isolation unless defined? Mock
140
-
141
- # +Stub+ is a synonym for +Isolation+ so you can still use it if you're that way inclined
142
- Stub = Isolation unless defined? Stub
143
-
1
+ require File.dirname(__FILE__) + '/isolator'
2
+ require File.dirname(__FILE__) + '/method_call_recorder'
3
+ require File.dirname(__FILE__) + '/expectation'
4
+ require File.dirname(__FILE__) + '/verification'
5
+
6
+ # Caricature - Bringing simple mocking to the DLR
7
+ # ===============================================
8
+ #
9
+ # This project aims to make interop between IronRuby objects and .NET objects easier.
10
+ # The idea is that it integrates nicely with bacon and later rspec and that it transparently lets you mock ironruby ojbects
11
+ # as well as CLR objects/interfaces.
12
+ # Caricature handles interfaces, interface inheritance, CLR objects, CLR object instances, Ruby classes and instances of Ruby classes.
13
+ #
14
+ # From the start I wanted to do away with names like mock, stub, record, replay, verify etc.
15
+ # Instead I took the advice from Roy Osherhove and went with a name of Isolation for creating a mock.
16
+ #
17
+ # An Isolation will create what in Rhino.Mocks would be called a DynamicMock (but can be a partial too) :)
18
+ # In Moq it would be the Loose mocking strategy.
19
+ #
20
+ # The naming of the methods for creating mocks is the one that JP Boodhoo proposed WhenToldTo and WasToldTo.
21
+ # To specify a stub/expectation on an isolation you have one and only one way of doing that with the method called when_receiving.
22
+ # Then only if you're interested in asserting if a method has been called you can use the did_receive? method for this.
23
+ #
24
+ #
25
+ # isolation = Isolation.for(Ninja)
26
+ # isolation.when_receiving(:attack) do |exp|
27
+ # exp.with(:shuriken)
28
+ # exp.return(3)
29
+ # end
30
+ #
31
+ # Battle.new(mock)
32
+ # battle.combat
33
+ #
34
+ # isolation.did_receive?(:attack).should.be.true?
35
+ #
36
+ #
37
+ # It may be very important to note that when you're going to be isolating CLR classes to be used in other CLR classes
38
+ # you still need to obide by the CLR rules. That means if you want to redefine a method you'll need to make sure it's
39
+ # marked as virtual. Static types are still governed by static type rules. I'm working on a solution around those
40
+ # problems but for the time being this is how it has to be.
41
+ module Caricature
42
+
43
+ # The context for an isolator. This contains the +subject+, +recorder+, +expectations+ and +messenger+
44
+ IsolatorContext = Struct.new(:subject, :recorder, :expectations, :messenger)
45
+
46
+ # Instead of using confusing terms like Mocking and Stubbing which is basically the same. Caricature tries
47
+ # to unify those concepts by using a term of *Isolation*.
48
+ # When you're testing you typically want to be in control of what you're testing. To do that you isolate
49
+ # dependencies and possibly define return values for methods on them.
50
+ # At a later stage you might be interested in which method was called, maybe even with which parameters
51
+ # or you might be interested in the amount of times it has been called.
52
+ class Isolation
53
+
54
+ # the real instance of the isolated subject
55
+ # used to forward calls in partial mocks
56
+ attr_accessor :instance
57
+
58
+ # the method call recorder
59
+ attr_reader :recorder
60
+
61
+ # the expecations set for this isolation
62
+ attr_reader :expectations
63
+
64
+ # Initializes a new instance of this isolation.
65
+ def initialize(isolator, context)
66
+ @instance = isolator.subject
67
+ @messenger = context.messenger
68
+ @messenger.recorder = @recorder = context.recorder
69
+ @expectations = context.expectations
70
+ @proxy = isolator.isolation
71
+ isolator.isolation.class.instance_variable_set("@___context___", self)
72
+ end
73
+
74
+ # record and send the message to the isolation.
75
+ # takes care of following expectations rules when sending messages.
76
+ def send_message(method_name, return_type, *args, &b)
77
+ @messenger.deliver(method_name, return_type, *args, &b)
78
+ end
79
+
80
+ # record and send the message to the isolation.
81
+ # takes care of following expectations rules when sending messages.
82
+ def send_class_message(method_name, return_type, *args, &b)
83
+ @messenger.deliver_to_class(method_name, return_type, *args, &b)
84
+ end
85
+
86
+ # builds up an expectation for an instance method, allows for overriding the result returned by the method
87
+ def create_override(method_name, &block)
88
+ internal_create_override method_name, :instance, &block
89
+ end
90
+
91
+ # builds up an expectation for a class method, allows for overriding the result returned by the class method
92
+ def create_class_override(method_name, &block)
93
+ internal_create_override method_name, :class, &block
94
+ end
95
+
96
+ # asserts whether the method has been called for the specified configuration
97
+ def verify(method_name, &block)
98
+ internal_verify method_name, :instance, &block
99
+ end
100
+
101
+ # asserts whether the method has been called for the specified configuration
102
+ def class_verify(method_name, &block)
103
+ internal_verify method_name, :class, &block
104
+ end
105
+
106
+ class << self
107
+
108
+ # Creates an isolation object complete with proxy and method call recorder
109
+ # It works out which isolation it needs to create and provide and initializes the
110
+ # method call recorder
111
+ def for(subject, recorder = MethodCallRecorder.new, expectations = Expectations.new)
112
+ context = IsolatorContext.new subject, recorder, expectations
113
+
114
+ isolator = RubyIsolator.for context
115
+ isolation = new(isolator, context)
116
+ isolation
117
+ end
118
+
119
+ end
120
+
121
+ protected
122
+
123
+ def internal_create_override(method_name, mode=:instance, &block)
124
+ builder = ExpectationBuilder.new method_name
125
+ block.call builder if block
126
+ exp = builder.build
127
+ expectations.add_expectation exp, mode
128
+ exp
129
+ end
130
+
131
+ def internal_verify(method_name, mode=:instance, &block)
132
+ verification = Verification.new(method_name, recorder, mode)
133
+ block.call verification unless block.nil?
134
+ verification
135
+ end
136
+ end
137
+
138
+ # +Mock+ is a synonym for +Isolation+ so you can still use it if you're that way inclined
139
+ Mock = Isolation unless defined? Mock
140
+
141
+ # +Stub+ is a synonym for +Isolation+ so you can still use it if you're that way inclined
142
+ Stub = Isolation unless defined? Stub
143
+
144
144
  end
@@ -1,303 +1,303 @@
1
- require 'rubygems'
2
- require 'uuidtools'
3
- require File.dirname(__FILE__) + '/messenger'
4
- require File.dirname(__FILE__) + '/descriptor'
5
-
6
- module Caricature
7
-
8
- # Groups the methods for interception together
9
- # this is a mix-in for the created isolations for classes
10
- module Interception
11
-
12
- # the class methods of this intercepting object
13
- module ClassMethods
14
-
15
- # the context of this isolation instance.
16
- # this context takes care of responding to method calls etc.
17
- def isolation_context
18
- @___context___
19
- end
20
-
21
- # Replaces the call to the proxy with the one you create with this method.
22
- # You can specify more specific criteria in the block to configure the expectation.
23
- #
24
- # Example:
25
- #
26
- # an_isolation.class.when_receiving(:a_method) do |method_call|
27
- # method_call.with(3, "a").return(5)
28
- # end
29
- #
30
- # is equivalent to:
31
- #
32
- # an_isolation.class.when_receiving(:a_method).with(3, "a").return(5)
33
- #
34
- # You will most likely use this method when you want your stubs to return something else than +nil+
35
- # when they get called during the run of the test they are defined in.
36
- def when_receiving(method_name, &block)
37
- isolation_context.create_class_override method_name, &block
38
- end
39
-
40
- # Verifies whether the specified method has been called
41
- # You can specify constraints in the block
42
- #
43
- # The most complex configuration you can make currently is one that is constrained by arguments.
44
- # This is most likely to be extended in the future to allow for more complex verifications.
45
- #
46
- # Example:
47
- #
48
- # an_isolation.class.did_receive?(:a_method) do |method_call|
49
- # method_call.with(3, "a")
50
- # end.should.be.successful
51
- #
52
- # is equivalent to:
53
- #
54
- # an_isolation.class.did_receive?(:a_method).with(3, "a").should.be.successful
55
- #
56
- # You will probably be using this method only when you're interested in whether a method has been called
57
- # during the course of the test you're running.
58
- def did_receive?(method_name, &block)
59
- isolation_context.class_verify method_name, &block
60
- end
61
-
62
- end
63
-
64
- # mixes in the class methods of this module when it gets included in a class.
65
- def self.included(base)
66
- base.extend ClassMethods
67
- end
68
-
69
- # the context of this isolation instance.
70
- # this context takes care of responding to method calls etc.
71
- def isolation_context
72
- self.class.isolation_context
73
- end
74
-
75
- # Replaces the call to the proxy with the one you create with this method.
76
- # You can specify more specific criteria in the block to configure the expectation.
77
- #
78
- # Example:
79
- #
80
- # an_isolation.when_receiving(:a_method) do |method_call|
81
- # method_call.with(3, "a").return(5)
82
- # end
83
- #
84
- # is equivalent to:
85
- #
86
- # an_isolation.when_receiving(:a_method).with(3, "a").return(5)
87
- #
88
- # You will most likely use this method when you want your stubs to return something else than +nil+
89
- # when they get called during the run of the test they are defined in.
90
- def when_receiving(method_name, &block)
91
- isolation_context.create_override method_name, &block
92
- end
93
-
94
- # Replaces the call to the class of the proxy with the one you create with this method.
95
- # You can specify more specific criteria in the block to configure the expectation.
96
- #
97
- # Example:
98
- #
99
- # an_isolation.when_class_receives(:a_method) do |method_call|
100
- # method_call.with(3, "a").return(5)
101
- # end
102
- #
103
- # is equivalent to:
104
- #
105
- # an_isolation.when_class_receives(:a_method).with(3, "a").return(5)
106
- #
107
- # You will most likely use this method when you want your stubs to return something else than +nil+
108
- # when they get called during the run of the test they are defined in.
109
- def when_class_receives(method_name, &block)
110
- self.class.when_receiving method_name, &block
111
- end
112
-
113
- # Verifies whether the specified method has been called
114
- # You can specify constraints in the block
115
- #
116
- # The most complex configuration you can make currently is one that is constrained by arguments.
117
- # This is most likely to be extended in the future to allow for more complex verifications.
118
- #
119
- # Example:
120
- #
121
- # an_isolation.did_receive?(:a_method) do |method_call|
122
- # method_call.with(3, "a")
123
- # end.should.be.successful
124
- #
125
- # is equivalent to:
126
- #
127
- # an_isolation.did_receive?(:a_method).with(3, "a").should.be.successful
128
- #
129
- # You will probably be using this method only when you're interested in whether a method has been called
130
- # during the course of the test you're running.
131
- def did_receive?(method_name, &block)
132
- isolation_context.verify method_name, &block
133
- end
134
-
135
- # Verifies whether the specified class method has been called
136
- # You can specify constraints in the block
137
- #
138
- # The most complex configuration you can make currently is one that is constrained by arguments.
139
- # This is likely to be extended in the future to allow for more complex verifications.
140
- #
141
- # Example:
142
- #
143
- # an_isolation.did_class_receive?(:a_method) do |method_call|
144
- # method_call.with(3, "a")
145
- # end.should.be.successful
146
- #
147
- # is equivalent to:
148
- #
149
- # an_isolation.did_class_receive?(:a_method).with(3, "a").should.be.successful
150
- #
151
- # You will probably be using this method only when you're interested in whether a method has been called
152
- # during the course of the test you're running.
153
- def did_class_receive?(method_name, &block)
154
- self.class.did_receive?(method_name, &block)
155
- end
156
-
157
- # Initializes the underlying subject
158
- # It expects the constructor parameters if they are needed.
159
- def with_subject(*args, &b)
160
- isolation_context.instance = self.class.superclass.new *args
161
- b.call self if b
162
- self
163
- end
164
-
165
- end
166
-
167
- # A base class for +Isolator+ objects
168
- # to stick with the +Isolation+ nomenclature the strategies for creating isolations
169
- # are called isolators.
170
- # An isolator functions as a barrier between the code in your test and the
171
- # underlying type/instance. It allows you to take control over the value
172
- # that is returned from a specific method, if you want to pass the method call along to
173
- # the underlying instance etc. It also contains the ability to verify if a method
174
- # was called, with which arguments etc.
175
- class Isolator
176
-
177
- # holds the isolation created by this isolator
178
- attr_reader :isolation
179
-
180
- # holds the subject of this isolator
181
- attr_reader :subject
182
-
183
- # holds the descriptor for this type of object
184
- attr_reader :descriptor
185
-
186
- # creates a new instance of an isolator
187
- def initialize(context)
188
- @context = context
189
- end
190
-
191
- # builds up the isolation class instance
192
- def build_isolation(klass, inst=nil)
193
- pxy = create_isolation_for klass
194
- @isolation = pxy.new
195
- @subject = inst
196
- initialize_messenger
197
- end
198
-
199
- # initializes the messaging strategy for the isolator
200
- def initialize_messenger
201
- raise NotImplementedError
202
- end
203
-
204
- # Creates the new class name for the isolation
205
- def class_name(subj)
206
- nm = subj.respond_to?(:class_eval) ? subj.demodulize : subj.class.demodulize
207
- @class_name = "#{nm}#{UUIDTools::UUID.random_create.to_s.gsub /-/, ''}"
208
- @class_name
209
- end
210
-
211
- # Sets up the necessary instance variables for the isolation
212
- def initialize_isolation(klass, context)
213
- pxy = klass.new
214
- pxy.instance_variable_set("@___context___", context)
215
- pxy
216
- end
217
-
218
-
219
- class << self
220
-
221
- # Creates the actual proxy object for the +subject+ and initializes it with a
222
- # +recorder+ and +expectations+
223
- # This is the actual isolation that will be used to in your tests.
224
- # It implements all the methods of the +subject+ so as long as you're in Ruby
225
- # and just need to isolate out some classes defined in a statically compiled language
226
- # it should get you all the way there for public instance methods at this point.
227
- # when you're going to isolation for usage within a statically compiled language type
228
- # then you're bound to most of their rules. So you need to either isolate interfaces
229
- # or mark the methods you want to isolate as virtual in your implementing classes.
230
- def for(context)
231
- context.recorder ||= MethodCallRecorder.new
232
- context.expectations ||= Expectations.new
233
- new(context)
234
- end
235
- end
236
- end
237
-
238
- # A proxy to Ruby objects that records method calls
239
- # this implements all the instance methods that are defined on the class.
240
- class RubyIsolator < Isolator
241
-
242
- # implemented template method for creating Ruby isolations
243
- def initialize(context)
244
- super
245
- klass = @context.subject.respond_to?(:class_eval) ? @context.subject : @context.subject.class
246
- inst = @context.subject.respond_to?(:class_eval) ? nil : @context.subject
247
- # inst = @context.subject.respond_to?(:class_eval) ? @context.subject.new : @context.subject
248
- @descriptor = RubyObjectDescriptor.new klass
249
- build_isolation klass, inst
250
- end
251
-
252
- # initializes the messaging strategy for the isolator
253
- def initialize_messenger
254
- @context.messenger = RubyMessenger.new @context.expectations, @subject
255
- end
256
-
257
- # creates the ruby isolator for the specified subject
258
- def create_isolation_for(subj)
259
- imembers = @descriptor.instance_members
260
- cmembers = @descriptor.class_members
261
-
262
- klass = Object.const_set(class_name(subj), Class.new(subj))
263
- klass.class_eval do
264
-
265
- include Interception
266
-
267
- # access to the proxied subject
268
- def ___super___
269
- isolation_context.instance
270
- end
271
-
272
- imembers.each do |mn|
273
- mn = mn.name.to_s.to_sym
274
- define_method mn do |*args|
275
- b = nil
276
- b = Proc.new { yield } if block_given?
277
- isolation_context.send_message(mn, nil, *args, &b)
278
- end
279
- end
280
-
281
- def initialize(*args)
282
- self
283
- end
284
-
285
- cmembers.each do |mn|
286
- mn = mn.name.to_s.to_sym
287
- define_cmethod mn do |*args|
288
- return if mn.to_s =~ /$(singleton_)?method_added/ and args.first.to_s =~ /$(singleton_)?method_added/
289
- b = nil
290
- b = Proc.new { yield } if block_given?
291
- isolation_context.send_class_message(mn, nil, *args, &b)
292
- end
293
- end
294
-
295
- end
296
-
297
- klass
298
- end
299
-
300
-
301
- end
302
-
1
+ require 'rubygems'
2
+ require 'uuidtools'
3
+ require File.dirname(__FILE__) + '/messenger'
4
+ require File.dirname(__FILE__) + '/descriptor'
5
+
6
+ module Caricature
7
+
8
+ # Groups the methods for interception together
9
+ # this is a mix-in for the created isolations for classes
10
+ module Interception
11
+
12
+ # the class methods of this intercepting object
13
+ module ClassMethods
14
+
15
+ # the context of this isolation instance.
16
+ # this context takes care of responding to method calls etc.
17
+ def isolation_context
18
+ @___context___
19
+ end
20
+
21
+ # Replaces the call to the proxy with the one you create with this method.
22
+ # You can specify more specific criteria in the block to configure the expectation.
23
+ #
24
+ # Example:
25
+ #
26
+ # an_isolation.class.when_receiving(:a_method) do |method_call|
27
+ # method_call.with(3, "a").return(5)
28
+ # end
29
+ #
30
+ # is equivalent to:
31
+ #
32
+ # an_isolation.class.when_receiving(:a_method).with(3, "a").return(5)
33
+ #
34
+ # You will most likely use this method when you want your stubs to return something else than +nil+
35
+ # when they get called during the run of the test they are defined in.
36
+ def when_receiving(method_name, &block)
37
+ isolation_context.create_class_override method_name, &block
38
+ end
39
+
40
+ # Verifies whether the specified method has been called
41
+ # You can specify constraints in the block
42
+ #
43
+ # The most complex configuration you can make currently is one that is constrained by arguments.
44
+ # This is most likely to be extended in the future to allow for more complex verifications.
45
+ #
46
+ # Example:
47
+ #
48
+ # an_isolation.class.did_receive?(:a_method) do |method_call|
49
+ # method_call.with(3, "a")
50
+ # end.should.be.successful
51
+ #
52
+ # is equivalent to:
53
+ #
54
+ # an_isolation.class.did_receive?(:a_method).with(3, "a").should.be.successful
55
+ #
56
+ # You will probably be using this method only when you're interested in whether a method has been called
57
+ # during the course of the test you're running.
58
+ def did_receive?(method_name, &block)
59
+ isolation_context.class_verify method_name, &block
60
+ end
61
+
62
+ end
63
+
64
+ # mixes in the class methods of this module when it gets included in a class.
65
+ def self.included(base)
66
+ base.extend ClassMethods
67
+ end
68
+
69
+ # the context of this isolation instance.
70
+ # this context takes care of responding to method calls etc.
71
+ def isolation_context
72
+ self.class.isolation_context
73
+ end
74
+
75
+ # Replaces the call to the proxy with the one you create with this method.
76
+ # You can specify more specific criteria in the block to configure the expectation.
77
+ #
78
+ # Example:
79
+ #
80
+ # an_isolation.when_receiving(:a_method) do |method_call|
81
+ # method_call.with(3, "a").return(5)
82
+ # end
83
+ #
84
+ # is equivalent to:
85
+ #
86
+ # an_isolation.when_receiving(:a_method).with(3, "a").return(5)
87
+ #
88
+ # You will most likely use this method when you want your stubs to return something else than +nil+
89
+ # when they get called during the run of the test they are defined in.
90
+ def when_receiving(method_name, &block)
91
+ isolation_context.create_override method_name, &block
92
+ end
93
+
94
+ # Replaces the call to the class of the proxy with the one you create with this method.
95
+ # You can specify more specific criteria in the block to configure the expectation.
96
+ #
97
+ # Example:
98
+ #
99
+ # an_isolation.when_class_receives(:a_method) do |method_call|
100
+ # method_call.with(3, "a").return(5)
101
+ # end
102
+ #
103
+ # is equivalent to:
104
+ #
105
+ # an_isolation.when_class_receives(:a_method).with(3, "a").return(5)
106
+ #
107
+ # You will most likely use this method when you want your stubs to return something else than +nil+
108
+ # when they get called during the run of the test they are defined in.
109
+ def when_class_receives(method_name, &block)
110
+ self.class.when_receiving method_name, &block
111
+ end
112
+
113
+ # Verifies whether the specified method has been called
114
+ # You can specify constraints in the block
115
+ #
116
+ # The most complex configuration you can make currently is one that is constrained by arguments.
117
+ # This is most likely to be extended in the future to allow for more complex verifications.
118
+ #
119
+ # Example:
120
+ #
121
+ # an_isolation.did_receive?(:a_method) do |method_call|
122
+ # method_call.with(3, "a")
123
+ # end.should.be.successful
124
+ #
125
+ # is equivalent to:
126
+ #
127
+ # an_isolation.did_receive?(:a_method).with(3, "a").should.be.successful
128
+ #
129
+ # You will probably be using this method only when you're interested in whether a method has been called
130
+ # during the course of the test you're running.
131
+ def did_receive?(method_name, &block)
132
+ isolation_context.verify method_name, &block
133
+ end
134
+
135
+ # Verifies whether the specified class method has been called
136
+ # You can specify constraints in the block
137
+ #
138
+ # The most complex configuration you can make currently is one that is constrained by arguments.
139
+ # This is likely to be extended in the future to allow for more complex verifications.
140
+ #
141
+ # Example:
142
+ #
143
+ # an_isolation.did_class_receive?(:a_method) do |method_call|
144
+ # method_call.with(3, "a")
145
+ # end.should.be.successful
146
+ #
147
+ # is equivalent to:
148
+ #
149
+ # an_isolation.did_class_receive?(:a_method).with(3, "a").should.be.successful
150
+ #
151
+ # You will probably be using this method only when you're interested in whether a method has been called
152
+ # during the course of the test you're running.
153
+ def did_class_receive?(method_name, &block)
154
+ self.class.did_receive?(method_name, &block)
155
+ end
156
+
157
+ # Initializes the underlying subject
158
+ # It expects the constructor parameters if they are needed.
159
+ def with_subject(*args, &b)
160
+ isolation_context.instance = self.class.superclass.new *args
161
+ b.call self if b
162
+ self
163
+ end
164
+
165
+ end
166
+
167
+ # A base class for +Isolator+ objects
168
+ # to stick with the +Isolation+ nomenclature the strategies for creating isolations
169
+ # are called isolators.
170
+ # An isolator functions as a barrier between the code in your test and the
171
+ # underlying type/instance. It allows you to take control over the value
172
+ # that is returned from a specific method, if you want to pass the method call along to
173
+ # the underlying instance etc. It also contains the ability to verify if a method
174
+ # was called, with which arguments etc.
175
+ class Isolator
176
+
177
+ # holds the isolation created by this isolator
178
+ attr_reader :isolation
179
+
180
+ # holds the subject of this isolator
181
+ attr_reader :subject
182
+
183
+ # holds the descriptor for this type of object
184
+ attr_reader :descriptor
185
+
186
+ # creates a new instance of an isolator
187
+ def initialize(context)
188
+ @context = context
189
+ end
190
+
191
+ # builds up the isolation class instance
192
+ def build_isolation(klass, inst=nil)
193
+ pxy = create_isolation_for klass
194
+ @isolation = pxy.new
195
+ @subject = inst
196
+ initialize_messenger
197
+ end
198
+
199
+ # initializes the messaging strategy for the isolator
200
+ def initialize_messenger
201
+ raise NotImplementedError
202
+ end
203
+
204
+ # Creates the new class name for the isolation
205
+ def class_name(subj)
206
+ nm = subj.respond_to?(:class_eval) ? subj.demodulize : subj.class.demodulize
207
+ @class_name = "#{nm}#{UUIDTools::UUID.random_create.to_s.gsub /-/, ''}"
208
+ @class_name
209
+ end
210
+
211
+ # Sets up the necessary instance variables for the isolation
212
+ def initialize_isolation(klass, context)
213
+ pxy = klass.new
214
+ pxy.instance_variable_set("@___context___", context)
215
+ pxy
216
+ end
217
+
218
+
219
+ class << self
220
+
221
+ # Creates the actual proxy object for the +subject+ and initializes it with a
222
+ # +recorder+ and +expectations+
223
+ # This is the actual isolation that will be used to in your tests.
224
+ # It implements all the methods of the +subject+ so as long as you're in Ruby
225
+ # and just need to isolate out some classes defined in a statically compiled language
226
+ # it should get you all the way there for public instance methods at this point.
227
+ # when you're going to isolation for usage within a statically compiled language type
228
+ # then you're bound to most of their rules. So you need to either isolate interfaces
229
+ # or mark the methods you want to isolate as virtual in your implementing classes.
230
+ def for(context)
231
+ context.recorder ||= MethodCallRecorder.new
232
+ context.expectations ||= Expectations.new
233
+ new(context)
234
+ end
235
+ end
236
+ end
237
+
238
+ # A proxy to Ruby objects that records method calls
239
+ # this implements all the instance methods that are defined on the class.
240
+ class RubyIsolator < Isolator
241
+
242
+ # implemented template method for creating Ruby isolations
243
+ def initialize(context)
244
+ super
245
+ klass = @context.subject.respond_to?(:class_eval) ? @context.subject : @context.subject.class
246
+ inst = @context.subject.respond_to?(:class_eval) ? nil : @context.subject
247
+ # inst = @context.subject.respond_to?(:class_eval) ? @context.subject.new : @context.subject
248
+ @descriptor = RubyObjectDescriptor.new klass
249
+ build_isolation klass, inst
250
+ end
251
+
252
+ # initializes the messaging strategy for the isolator
253
+ def initialize_messenger
254
+ @context.messenger = RubyMessenger.new @context.expectations, @subject
255
+ end
256
+
257
+ # creates the ruby isolator for the specified subject
258
+ def create_isolation_for(subj)
259
+ imembers = @descriptor.instance_members
260
+ cmembers = @descriptor.class_members
261
+
262
+ klass = Object.const_set(class_name(subj), Class.new(subj))
263
+ klass.class_eval do
264
+
265
+ include Interception
266
+
267
+ # access to the proxied subject
268
+ def ___super___
269
+ isolation_context.instance
270
+ end
271
+
272
+ imembers.each do |mn|
273
+ mn = mn.name.to_s.to_sym
274
+ define_method mn do |*args|
275
+ b = nil
276
+ b = Proc.new { yield } if block_given?
277
+ isolation_context.send_message(mn, nil, *args, &b)
278
+ end
279
+ end
280
+
281
+ def initialize(*args)
282
+ self
283
+ end
284
+
285
+ cmembers.each do |mn|
286
+ mn = mn.name.to_s.to_sym
287
+ define_cmethod mn do |*args|
288
+ return if mn.to_s =~ /$(singleton_)?method_added/ and args.first.to_s =~ /$(singleton_)?method_added/
289
+ b = nil
290
+ b = Proc.new { yield } if block_given?
291
+ isolation_context.send_class_message(mn, nil, *args, &b)
292
+ end
293
+ end
294
+
295
+ end
296
+
297
+ klass
298
+ end
299
+
300
+
301
+ end
302
+
303
303
  end