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.
- data/caricature.gemspec +3 -3
- data/lib/bin/Workarounds.dll.mdb +0 -0
- data/lib/caricature.rb +25 -25
- data/lib/caricature/clr.rb +6 -6
- data/lib/caricature/clr/aspnet_mvc.rb +54 -54
- data/lib/caricature/core_ext.rb +10 -10
- data/lib/caricature/core_ext/array.rb +9 -9
- data/lib/caricature/core_ext/class.rb +31 -14
- data/lib/caricature/core_ext/hash.rb +12 -12
- data/lib/caricature/core_ext/module.rb +14 -14
- data/lib/caricature/core_ext/object.rb +76 -18
- data/lib/caricature/core_ext/string.rb +16 -16
- data/lib/caricature/core_ext/system/string.rb +20 -20
- data/lib/caricature/core_ext/system/type.rb +26 -26
- data/lib/caricature/descriptor.rb +73 -73
- data/lib/caricature/expectation.rb +264 -263
- data/lib/caricature/isolation.rb +143 -143
- data/lib/caricature/isolator.rb +302 -302
- data/lib/caricature/messenger.rb +67 -67
- data/lib/caricature/method_call_recorder.rb +228 -228
- data/lib/caricature/verification.rb +60 -60
- data/lib/caricature/version.rb +1 -1
- data/spec/bacon/integration/clr_to_clr_spec.rb +4 -4
- data/spec/bacon/integration/clr_to_ruby_spec.rb +227 -227
- data/spec/bacon/integration/event_spec.rb +2 -2
- data/spec/bacon/integration/ruby_to_ruby_spec.rb +270 -270
- data/spec/bacon/integration/syntax_spec.rb +43 -0
- data/spec/bacon/unit/core_ext_spec.rb +87 -87
- data/spec/bacon/unit/expectation_spec.rb +300 -300
- data/spec/bacon/unit/interop_spec.rb +29 -29
- data/spec/bacon/unit/isolation_spec.rb +86 -86
- data/spec/bacon/unit/isolator_spec.rb +219 -219
- data/spec/bacon/unit/messaging_spec.rb +310 -310
- data/spec/bacon/unit/method_call_spec.rb +342 -342
- data/spec/bin/ClrModels.dll.mdb +0 -0
- data/spec/rspec/unit/event_spec.rb +1 -1
- metadata +31 -11
- data/spec/models.notused/ClrModels.cs +0 -241
- data/spec/models.notused/ruby_models.rb +0 -151
data/lib/caricature/isolation.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
data/lib/caricature/isolator.rb
CHANGED
@@ -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
|