caricature 0.3.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (129) hide show
  1. data/.gitignore +4 -0
  2. data/Rakefile +100 -0
  3. data/VERSION +1 -0
  4. data/caricature.gemspec +177 -0
  5. data/doc/Array.html +251 -0
  6. data/doc/Caricature/ArgumentRecording.html +366 -0
  7. data/doc/Caricature/ClrClassDescriptor.html +303 -0
  8. data/doc/Caricature/ClrClassMessenger.html +263 -0
  9. data/doc/Caricature/ClrInterfaceDescriptor.html +295 -0
  10. data/doc/Caricature/ClrInterfaceIsolator.html +365 -0
  11. data/doc/Caricature/ClrInterfaceMessenger.html +260 -0
  12. data/doc/Caricature/ClrIsolator.html +424 -0
  13. data/doc/Caricature/Expectation.html +549 -0
  14. data/doc/Caricature/ExpectationBuilder.html +308 -0
  15. data/doc/Caricature/ExpectationSyntax.html +438 -0
  16. data/doc/Caricature/Expectations.html +344 -0
  17. data/doc/Caricature/Interception/ClassMethods.html +246 -0
  18. data/doc/Caricature/Interception.html +416 -0
  19. data/doc/Caricature/Isolation.html +578 -0
  20. data/doc/Caricature/Isolator.html +519 -0
  21. data/doc/Caricature/MemberDescriptor.html +335 -0
  22. data/doc/Caricature/Messenger.html +335 -0
  23. data/doc/Caricature/MethodCallRecorder.html +440 -0
  24. data/doc/Caricature/MethodCallRecording.html +493 -0
  25. data/doc/Caricature/RubyIsolator.html +416 -0
  26. data/doc/Caricature/RubyMessenger.html +260 -0
  27. data/doc/Caricature/RubyObjectDescriptor.html +289 -0
  28. data/doc/Caricature/TypeDescriptor.html +378 -0
  29. data/doc/Caricature/Verification.html +443 -0
  30. data/doc/Caricature.html +358 -0
  31. data/doc/Class.html +287 -0
  32. data/doc/Hash.html +255 -0
  33. data/doc/Module.html +287 -0
  34. data/doc/Object.html +322 -0
  35. data/doc/README_markdown.html +241 -0
  36. data/doc/String.html +289 -0
  37. data/doc/System/String.html +289 -0
  38. data/doc/System/Type.html +289 -0
  39. data/doc/System.html +207 -0
  40. data/doc/created.rid +1 -0
  41. data/doc/images/brick.png +0 -0
  42. data/doc/images/brick_link.png +0 -0
  43. data/doc/images/bug.png +0 -0
  44. data/doc/images/bullet_black.png +0 -0
  45. data/doc/images/bullet_toggle_minus.png +0 -0
  46. data/doc/images/bullet_toggle_plus.png +0 -0
  47. data/doc/images/date.png +0 -0
  48. data/doc/images/find.png +0 -0
  49. data/doc/images/loadingAnimation.gif +0 -0
  50. data/doc/images/macFFBgHack.png +0 -0
  51. data/doc/images/package.png +0 -0
  52. data/doc/images/page_green.png +0 -0
  53. data/doc/images/page_white_text.png +0 -0
  54. data/doc/images/page_white_width.png +0 -0
  55. data/doc/images/plugin.png +0 -0
  56. data/doc/images/ruby.png +0 -0
  57. data/doc/images/tag_green.png +0 -0
  58. data/doc/images/wrench.png +0 -0
  59. data/doc/images/wrench_orange.png +0 -0
  60. data/doc/images/zoom.png +0 -0
  61. data/doc/index.html +312 -0
  62. data/doc/js/darkfish.js +116 -0
  63. data/doc/js/jquery.js +32 -0
  64. data/doc/js/quicksearch.js +114 -0
  65. data/doc/js/thickbox-compressed.js +10 -0
  66. data/doc/lib/caricature/clr/descriptor_rb.html +52 -0
  67. data/doc/lib/caricature/clr/isolation_rb.html +52 -0
  68. data/doc/lib/caricature/clr/isolator_rb.html +52 -0
  69. data/doc/lib/caricature/clr/messenger_rb.html +52 -0
  70. data/doc/lib/caricature/clr_rb.html +52 -0
  71. data/doc/lib/caricature/descriptor_rb.html +52 -0
  72. data/doc/lib/caricature/expectation_rb.html +52 -0
  73. data/doc/lib/caricature/isolation_rb.html +52 -0
  74. data/doc/lib/caricature/isolator_rb.html +52 -0
  75. data/doc/lib/caricature/messaging_rb.html +52 -0
  76. data/doc/lib/caricature/method_call_recorder_rb.html +52 -0
  77. data/doc/lib/caricature/verification_rb.html +52 -0
  78. data/doc/lib/caricature_rb.html +52 -0
  79. data/doc/lib/core_ext/array_rb.html +52 -0
  80. data/doc/lib/core_ext/class_rb.html +52 -0
  81. data/doc/lib/core_ext/core_ext_rb.html +52 -0
  82. data/doc/lib/core_ext/hash_rb.html +52 -0
  83. data/doc/lib/core_ext/module_rb.html +52 -0
  84. data/doc/lib/core_ext/object_rb.html +52 -0
  85. data/doc/lib/core_ext/string_rb.html +52 -0
  86. data/doc/lib/core_ext/system/string_rb.html +52 -0
  87. data/doc/lib/core_ext/system/type_rb.html +52 -0
  88. data/doc/rdoc.css +696 -0
  89. data/irb_init.rb +9 -0
  90. data/lib/bin/Workarounds.dll +0 -0
  91. data/lib/bin/Workarounds.pdb +0 -0
  92. data/lib/caricature/clr/descriptor.rb +55 -0
  93. data/lib/caricature/clr/isolation.rb +33 -0
  94. data/lib/caricature/clr/isolator.rb +112 -0
  95. data/lib/caricature/clr/messenger.rb +46 -0
  96. data/lib/caricature/clr.rb +6 -0
  97. data/lib/caricature/descriptor.rb +74 -0
  98. data/lib/caricature/expectation.rb +159 -0
  99. data/lib/caricature/isolation.rb +146 -0
  100. data/lib/caricature/isolator.rb +287 -0
  101. data/lib/caricature/messenger.rb +57 -0
  102. data/lib/caricature/method_call_recorder.rb +130 -0
  103. data/lib/caricature/verification.rb +43 -0
  104. data/lib/caricature.rb +11 -0
  105. data/lib/core_ext/array.rb +10 -0
  106. data/lib/core_ext/class.rb +15 -0
  107. data/lib/core_ext/core_ext.rb +8 -0
  108. data/lib/core_ext/hash.rb +13 -0
  109. data/lib/core_ext/module.rb +15 -0
  110. data/lib/core_ext/object.rb +19 -0
  111. data/lib/core_ext/string.rb +17 -0
  112. data/lib/core_ext/system/string.rb +21 -0
  113. data/lib/core_ext/system/type.rb +21 -0
  114. data/pkg/.gitignore +0 -0
  115. data/pkg/caricature-0.1.0.gem +0 -0
  116. data/spec/bacon_helper.rb +49 -3
  117. data/spec/bin/.gitignore +0 -0
  118. data/spec/core_ext_spec.rb +9 -0
  119. data/spec/descriptor_spec.rb +142 -0
  120. data/spec/expectation_spec.rb +11 -7
  121. data/spec/integration_spec.rb +94 -2
  122. data/spec/isolation_spec.rb +0 -42
  123. data/spec/isolator_spec.rb +72 -66
  124. data/spec/messaging_spec.rb +172 -0
  125. data/spec/method_call_spec.rb +21 -21
  126. data/spec/models/ClrModels.cs +185 -0
  127. data/spec/verification_spec.rb +1 -1
  128. data/workarounds/ReflectionHelper.cs +30 -0
  129. metadata +133 -2
@@ -0,0 +1,55 @@
1
+ module Caricature
2
+
3
+ # describes clr interfaces.
4
+ # Because CLR interfaces can't have static members this descriptor does not collect any class members
5
+ class ClrInterfaceDescriptor < TypeDescriptor
6
+
7
+ # collects instance members on this interface
8
+ # it will collect properties, methods and property setters
9
+ def initialize_instance_members_for(klass)
10
+ clr_type = klass.to_clr_type
11
+
12
+ properties = clr_type.collect_interface_properties
13
+ methods = clr_type.collect_interface_methods
14
+
15
+ @instance_members += methods.collect { |mi| MemberDescriptor.new(mi.name.underscore, mi.return_type) }
16
+ @instance_members += properties.collect { |pi| MemberDescriptor.new(pi.name.underscore, pi.property_type) }
17
+ @instance_members += properties.select { |pi| pi.can_write }.collect { |pi| MemberDescriptor.new("#{pi.name.underscore}=", nil) }
18
+ end
19
+
20
+ # this method is empty because an interface can't have static members
21
+ def initialize_class_members_for(klass); end
22
+
23
+ end
24
+
25
+
26
+ # Describes a CLR class type. it collects the properties and methods on an instance as well as on a static level
27
+ class ClrClassDescriptor < TypeDescriptor
28
+
29
+ # collects all the instance members of the provided CLR class type
30
+ def initialize_instance_members_for(klass)
31
+ clr_type = klass.to_clr_type
32
+
33
+ methods = Workarounds::ReflectionHelper.get_instance_methods(clr_type)
34
+ properties = Workarounds::ReflectionHelper.get_instance_properties(clr_type)
35
+
36
+ @instance_members += methods.collect { |mi| MemberDescriptor.new(mi.name.underscore, mi.return_type) }
37
+ @instance_members += properties.collect { |pi| MemberDescriptor.new(pi.name.underscore, pi.property_type) }
38
+ @instance_members += properties.select{|pi| pi.can_write }.collect { |pi| MemberDescriptor.new("#{pi.name.underscore}=", nil) }
39
+ end
40
+
41
+ # collects all the static members of the provided CLR class type
42
+ def initialize_class_members_for(klass)
43
+ clr_type = klass.to_clr_type
44
+
45
+ methods = Workarounds::ReflectionHelper.get_class_methods(clr_type)
46
+ properties = Workarounds::ReflectionHelper.get_class_properties(clr_type)
47
+
48
+ @class_members += methods.collect { |mi| MemberDescriptor.new(mi.name.underscore, mi.return_type, false) }
49
+ @class_members += properties.collect { |pi| MemberDescriptor.new(pi.name.underscore, pi.property_type, false) }
50
+ @class_members += properties.select{|pi| pi.can_write }.collect { |pi| MemberDescriptor.new("#{pi.name.underscore}=", nil, false) }
51
+ end
52
+
53
+ end
54
+
55
+ end
@@ -0,0 +1,33 @@
1
+ module Caricature
2
+
3
+ class Isolation
4
+
5
+ class << self
6
+
7
+ # Creates an isolation object complete with proxy and method call recorder
8
+ # It works out which isolation it needs to create and provide and initializes the
9
+ # method call recorder
10
+ def for(subject, recorder = MethodCallRecorder.new, expectations = Expectations.new)
11
+ context = IsolatorContext.new subject, recorder, expectations
12
+ isolation_strategy = subject.is_clr_type? ? get_clr_isolation_strategy(subject) : RubyIsolator
13
+
14
+ isolator = isolation_strategy.for context
15
+ isolation = new(isolator, context)
16
+ isolator.isolation
17
+ end
18
+
19
+ private
20
+
21
+ # decides which startegy to use for mocking a CLR object.
22
+ # When the provided subject is an interface it will return a +ClrInterfaceIsolator+
23
+ # otherwise it will return a +ClrIsolator+
24
+ def get_clr_isolation_strategy(subject)
25
+ return ClrInterfaceIsolator if subject.respond_to? :class_eval and !subject.respond_to? :new
26
+ ClrIsolator
27
+ end
28
+ end
29
+
30
+
31
+ end
32
+
33
+ end
@@ -0,0 +1,112 @@
1
+ module Caricature
2
+
3
+ # A proxy to CLR objects that records method calls.
4
+ # this implements all the public instance methods of the class when you use it in ruby code
5
+ # When you use it in a CLR class you're bound to CLR rules and it only overrides the methods
6
+ # that are marked as virtual. We also can't isolate static or sealed types at the moment.
7
+ class ClrIsolator < Isolator
8
+
9
+
10
+ # Implementation of the template method that creates
11
+ # an isolator for a class defined in a CLR language.
12
+ def initialize(context)
13
+ super
14
+ instance = nil
15
+ sklass = context.subject
16
+ unless context.subject.respond_to?(:class_eval)
17
+ sklass = context.subject.class
18
+ instance = context.subject
19
+ end
20
+ @descriptor = ClrClassDescriptor.new sklass
21
+ build_isolation sklass, (instance || sklass.new)
22
+ end
23
+
24
+ # initializes the messaging strategy for the isolator
25
+ def initialize_messenger
26
+ @context.messenger = ClrClassMessenger.new @context.expectations, @subject
27
+ end
28
+
29
+ # builds the Isolator class for the specified subject
30
+ def create_isolation_for(subj)
31
+ members = @descriptor.instance_members
32
+ class_members = @descriptor.class_members
33
+
34
+ klass = Object.const_set(class_name(subj), Class.new(subj))
35
+ klass.class_eval do
36
+
37
+ include Interception
38
+
39
+ # access to the proxied subject
40
+ def ___super___
41
+ isolation_context.instance
42
+ end
43
+
44
+ members.each do |mem|
45
+ nm = mem.name.to_s.to_sym
46
+ define_method nm do |*args|
47
+ b = nil
48
+ b = Proc.new { yield } if block_given?
49
+ isolation_context.send_message(nm, mem.return_type, *args, &b)
50
+ end unless nm == :to_string
51
+ end
52
+
53
+ class_members.each do |mn|
54
+ mn = mn.name.to_s.to_sym
55
+ define_cmethod mn do |*args|
56
+ b = nil
57
+ b = Proc.new { yield } if block_given?
58
+ isolation_context.send_class_message(mn, nil, *args, &b)
59
+ end
60
+ end
61
+
62
+ end
63
+
64
+ klass
65
+ end
66
+
67
+ end
68
+
69
+ # An +Isolator+ for CLR interfaces.
70
+ # this implements all the methods that are defined on the interface.
71
+ class ClrInterfaceIsolator < Isolator
72
+
73
+ # Implementation of the template method that creates
74
+ # an isolator for an interface defined in a CLR language.
75
+ def initialize(context)
76
+ super
77
+ sklass = context.subject
78
+ @descriptor = ClrInterfaceDescriptor.new sklass
79
+ build_isolation sklass
80
+ end
81
+
82
+ # initializes the messaging strategy for the isolator
83
+ def initialize_messenger
84
+ @context.messenger = ClrInterfaceMessenger.new @context.expectations
85
+ end
86
+
87
+ # builds the actual +isolator+ for the CLR interface
88
+ def create_isolation_for(subj)
89
+ proxy_members = @descriptor.instance_members
90
+
91
+ klass = Object.const_set(class_name(subj), Class.new)
92
+ klass.class_eval do
93
+
94
+ include subj
95
+ include Interception
96
+
97
+ proxy_members.each do |mem|
98
+ nm = mem.name.to_s.to_sym
99
+ define_method nm do |*args|
100
+ b = nil
101
+ b = Proc.new { yield } if block_given?
102
+ isolation_context.send_message(nm, mem.return_type, *args, &b)
103
+ end
104
+ end
105
+
106
+ end
107
+
108
+ klass
109
+ end
110
+ end
111
+
112
+ end
@@ -0,0 +1,46 @@
1
+ module Caricature
2
+
3
+ # Encapsulates sending messages to CLR class or instance isolations
4
+ class ClrClassMessenger < Messenger
5
+
6
+ protected
7
+
8
+ # deliver the message to the receiving isolation
9
+ def internal_deliver(mode, method_name, return_type, *args, &b)
10
+ exp = expectations.find(method_name, mode, *args)
11
+ if exp
12
+ res = instance.__send__(method_name, *args, &b) if exp.super_before?
13
+ res = exp.execute *args
14
+ res = instance.__send__(method_name, *args, &b) if !exp.super_before? and exp.call_super?
15
+ res
16
+ else
17
+ rt = nil
18
+ is_value_type = return_type && return_type != System::Void.to_clr_type && return_type.is_value_type
19
+ rt = System::Activator.create_instance(return_type) if is_value_type
20
+ rt
21
+ end
22
+ end
23
+
24
+ end
25
+
26
+ # Encapsulates sending messages to CLR interface isolations
27
+ class ClrInterfaceMessenger < Messenger
28
+
29
+ protected
30
+
31
+ # deliver the message to the receiving isolation
32
+ def internal_deliver(mode, method_name, return_type, *args, &b)
33
+ exp = expectations.find(method_name, mode, *args)
34
+ if exp
35
+ res = exp.execute *args
36
+ res
37
+ else
38
+ rt = nil
39
+ rt = System::Activator.create_instance(return_type) if return_type && return_type != System::Void.to_clr_type && return_type.is_value_type
40
+ rt
41
+ end
42
+ end
43
+
44
+ end
45
+
46
+ end
@@ -0,0 +1,6 @@
1
+ load_assembly 'Workarounds, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null'
2
+
3
+ require File.dirname(__FILE__) + "/clr/descriptor"
4
+ require File.dirname(__FILE__) + "/clr/messenger"
5
+ require File.dirname(__FILE__) + "/clr/isolator"
6
+ require File.dirname(__FILE__) + "/clr/isolation"
@@ -0,0 +1,74 @@
1
+ module Caricature
2
+
3
+ # Describes a class/type so it can be proxied more easily
4
+ # This is the base class from which other more specialised descriptors can be
5
+ # used to provide the metadata need to generate the proxy classes
6
+ class TypeDescriptor
7
+
8
+ # Gets the instance members of the described type/class
9
+ attr_reader :instance_members
10
+
11
+ # Gets the class members for the described type/class
12
+ attr_reader :class_members
13
+
14
+ # initializes a new instance of a type descriptor
15
+ def initialize(klass)
16
+ @instance_members = []
17
+ @class_members = []
18
+
19
+ unless klass == :in_unit_test_for_class
20
+ initialize_instance_members_for klass
21
+ initialize_class_members_for klass
22
+ end
23
+ end
24
+
25
+ # collects the instance members of the provided type
26
+ def initialize_instance_members_for(klass)
27
+ raise NotImplementedError.new("Override in implementing class")
28
+ end
29
+
30
+ # collects the class members of the provided type
31
+ def initialize_class_members_for(klass)
32
+ raise NotImplementedError.new("Override in implementing class")
33
+ end
34
+ end
35
+
36
+ # Describes a member of a class
37
+ # this contains the metadata needed to generate the member
38
+ # and perhaps a default value for the property type if necessary
39
+ class MemberDescriptor
40
+
41
+ # the return type of this member
42
+ attr_reader :return_type
43
+
44
+ # the name of this member
45
+ attr_reader :name
46
+
47
+ # initializes a nem instance of member descriptor
48
+ def initialize(name, return_type=nil, is_instance_member=true)
49
+ @name, @return_type, @instance_member = name, return_type, is_instance_member
50
+ end
51
+
52
+ # indicates whether this member is a class member or an instance member
53
+ def instance_member?
54
+ @instance_member
55
+ end
56
+
57
+ end
58
+
59
+ # Describes a ruby object.
60
+ class RubyObjectDescriptor < TypeDescriptor
61
+
62
+ # collects all the members that are defined by this class
63
+ def initialize_instance_members_for(klass)
64
+ @instance_members += klass.instance_methods(false).collect { |mn| MemberDescriptor.new(mn) }
65
+ end
66
+
67
+ # collects all the members that aren't a member of Object.singleton_methods
68
+ def initialize_class_members_for(klass)
69
+ @class_members += klass.methods(false).collect { |mn| MemberDescriptor.new(mn) }
70
+ end
71
+
72
+ end
73
+
74
+ end
@@ -0,0 +1,159 @@
1
+ module Caricature
2
+
3
+ # A collection of expectations with some methods to make it easier to work with them.
4
+ # It allows you to add and find expectations based on certain criteria.
5
+ class Expectations
6
+
7
+ #initializes a new empty instance of the +Expectation+ collection
8
+ def initialize
9
+ @instance_expectations = []
10
+ @class_expectations = []
11
+ end
12
+
13
+ # Adds an expectation to this collection. From then on it can be found in the collection.
14
+ def add_expectation(expectation, mode=:instance)
15
+ @instance_expectations << expectation unless mode == :class
16
+ @class_expectations << expectation if mode == :class
17
+ end
18
+
19
+ # Finds an expectation in the collection. It matches by +method_name+ first.
20
+ # Then it tries to figure out if you care about the arguments. You can say you don't care by providing
21
+ # the symbol +:any+ as first argument to this method. When you don't care the first match is being returned
22
+ # When you specify arguments other than +:any+ it will try to match the specified arguments in addition
23
+ # to the method name. It will then also return the first result it can find.
24
+ def find(method_name, mode=:instance, *args)
25
+ expectations = mode == :class ? @class_expectations : @instance_expectations
26
+ candidates = expectations.select { |exp| exp.method_name.to_s.to_sym == method_name.to_s.to_sym }
27
+ is_single = args.empty? || args.first == :any || (candidates.size == 1 && candidates.first.any_args?)
28
+ return candidates.first if is_single
29
+
30
+ second_pass = candidates.select {|exp| exp.args == args }
31
+ second_pass.first
32
+ end
33
+
34
+ end
35
+
36
+ # contains the syntax for building up an expectation
37
+ # This is shared between the +ExpecationBuilder+ and the +Expectation+
38
+ module ExpectationSyntax
39
+ # tell the expection which arguments it needs to respond to
40
+ # there is a magic argument here +any+ which configures
41
+ # the expectation to respond to any arguments
42
+ def with(*args)
43
+ @any_args = false unless args.first == :any
44
+ @args = args
45
+ self
46
+ end
47
+
48
+ # tell the expectation it nees to return this value or the value returned by the block
49
+ # you provide to this method.
50
+ def return(value=nil)
51
+ @return_value = value
52
+ @return_value ||= yield if block_given?
53
+ self
54
+ end
55
+
56
+ # tell the expectation it needs to raise an error with the specified arguments
57
+ def raise(*args)
58
+ @error_args = args
59
+ self
60
+ end
61
+
62
+ # tell the expecation it needs to call the super before the expectation exectution
63
+ def super_before
64
+ @super = :before
65
+ end
66
+
67
+ # tell the expectation it needs to call the super after the expecation execution
68
+ def super_after
69
+ @super = :after
70
+ end
71
+
72
+ # indicates whether this expectation should match with any arguments
73
+ # or only for the specified arguments
74
+ def any_args?
75
+ @any_args
76
+ end
77
+ end
78
+
79
+ # A description of an expectation.
80
+ # An expectation has the power to call the proxy method or completely ignore it
81
+ class Expectation
82
+
83
+ include ExpectationSyntax
84
+
85
+ # gets the method_name to which this expectation needs to listen to
86
+ attr_reader :method_name
87
+
88
+ # the arguments that this expectation needs to be constrained by
89
+ attr_reader :args
90
+
91
+ # the error_args that this expectation will raise an error with
92
+ attr_reader :error_args
93
+
94
+ # the value that this expecation will return when executed
95
+ attr_reader :return_value
96
+
97
+ # indicator for the mode to call the super +:before+, +:after+ and +nil+
98
+ attr_reader :super
99
+
100
+ # Initializes a new instance of an expectation
101
+ def initialize(method_name, args, error_args, return_value, super_mode)
102
+ @method_name, @args, @error_args, @return_value, @super =
103
+ method_name, args, error_args, return_value, super_mode
104
+ @any_args = true
105
+ end
106
+
107
+ # indicates whether this expecation will raise an event.
108
+ def has_error_args?
109
+ !@error_args.nil?
110
+ end
111
+
112
+ # indicates whether this expectation will return a value.
113
+ def has_return_value?
114
+ !@return_value.nil?
115
+ end
116
+
117
+ # call the super before the expectation
118
+ def super_before?
119
+ @super == :before
120
+ end
121
+
122
+ # indicates whether super needs to be called somewhere
123
+ def call_super?
124
+ !@super.nil?
125
+ end
126
+
127
+ # executes this expectation with its configuration
128
+ def execute(*margs)
129
+ ags = any_args? ? (margs.empty? ? :any : margs) : args
130
+ raise *@error_args if has_error_args?
131
+ return return_value if has_return_value?
132
+ nil
133
+ end
134
+ end
135
+
136
+ # Constructs the expecation object.
137
+ # Used as a boundary between the definition and usage of the expectation
138
+ class ExpectationBuilder
139
+
140
+ include ExpectationSyntax
141
+
142
+ # initialises a new instance of the expectation builder
143
+ # this builder is passed into the block to allow only certain
144
+ # operations in the block.
145
+ def initialize(method_name)
146
+ @method_name, @return_value, @super, @block, @error_args, @args, @any_args =
147
+ method_name, nil, nil, nil, nil, [], true
148
+ end
149
+
150
+
151
+
152
+ # build up the expectation with the provided arguments
153
+ def build
154
+ Expectation.new @method_name, @args, @error_args, @return_value, @super
155
+ end
156
+
157
+ end
158
+
159
+ end
@@ -0,0 +1,146 @@
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_reader :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
+ @recorder = context.recorder
68
+ @messenger = context.messenger
69
+ @expectations = context.expectations
70
+ isolator.isolation.class.instance_variable_set("@___context___", self)
71
+ end
72
+
73
+ # record and send the message to the isolation.
74
+ # takes care of following expectations rules when sending messages.
75
+ def send_message(method_name, return_type, *args, &b)
76
+ recorder.record_call method_name, :instance, *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
+ recorder.record_call method_name, :class, *args, &b
84
+ @messenger.deliver_to_class(method_name, return_type, *args, &b)
85
+ end
86
+
87
+ # builds up an expectation for an instance method, allows for overriding the result returned by the method
88
+ def create_override(method_name, &block)
89
+ internal_create_override method_name, :instance, &block
90
+ end
91
+
92
+ # builds up an expectation for a class method, allows for overriding the result returned by the class method
93
+ def create_class_override(method_name, &block)
94
+ puts "creating override"
95
+ internal_create_override method_name, :class, &block
96
+ end
97
+
98
+ # asserts whether the method has been called for the specified configuration
99
+ def verify(method_name, &block)
100
+ internal_verify method_name, :instance, &block
101
+ end
102
+
103
+ # asserts whether the method has been called for the specified configuration
104
+ def class_verify(method_name, &block)
105
+ internal_verify method_name, :class, &block
106
+ end
107
+
108
+ class << self
109
+
110
+ # Creates an isolation object complete with proxy and method call recorder
111
+ # It works out which isolation it needs to create and provide and initializes the
112
+ # method call recorder
113
+ def for(subject, recorder = MethodCallRecorder.new, expectations = Expectations.new)
114
+ context = IsolatorContext.new subject, recorder, expectations
115
+
116
+ isolator = RubyIsolator.for context
117
+ isolation = new(isolator, context)
118
+ isolator.isolation
119
+ end
120
+
121
+ end
122
+
123
+ protected
124
+
125
+ def internal_create_override(method_name, mode=:instance, &block)
126
+ builder = ExpectationBuilder.new method_name
127
+ block.call builder unless block.nil?
128
+ exp = builder.build
129
+ expectations.add_expectation exp, mode
130
+ exp
131
+ end
132
+
133
+ def internal_verify(method_name, mode=:instance, &block)
134
+ verification = Verification.new(method_name, recorder, mode)
135
+ block.call verification unless block.nil?
136
+ verification
137
+ end
138
+ end
139
+
140
+ # +Mock+ is a synonym for +Isolation+ so you can still use it if you're that way inclined
141
+ Mock = Isolation unless defined? Mock
142
+
143
+ # +Stub+ is a synonym for +Isolation+ so you can still use it if you're that way inclined
144
+ Stub = Isolation unless defined? Stub
145
+
146
+ end