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