caricature 0.7.6 → 0.7.7

Sign up to get free protection for your applications and to get access to all the features.
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