mockr 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. data/lib/mockr.rb +222 -0
  2. data/test/mockr_tests.rb +165 -0
  3. metadata +47 -0
data/lib/mockr.rb ADDED
@@ -0,0 +1,222 @@
1
+ require 'test/unit'
2
+
3
+ module Mockr
4
+
5
+ ################################################################################
6
+
7
+ class Mock
8
+ include ::Test::Unit
9
+
10
+ attr_reader :proxy
11
+
12
+ # Create a new Mock, with an optional initialisation block. If provided,
13
+ # the block will be called with the new instance.
14
+ #
15
+ # Example:
16
+ # Mock.new do |m|
17
+ # m.stubs.some_method
18
+ # end
19
+ def initialize &block
20
+ @expectations = []
21
+ @satisfied_expectations = []
22
+ create_proxy
23
+ block.call(self) if block
24
+ end
25
+
26
+ # Tell the mock to respond to a call, optionally with specific parameters.
27
+ #
28
+ # The call can be called an arbitrary number of times by the client code
29
+ # without affecting the result of #verify.
30
+ #
31
+ # Parameters to the expected call will be used to match the actual parameters
32
+ # passed by client code later. The match (===) method of the expectation
33
+ # parameter is used to determine whether the client's call matched this
34
+ # anticipated call.
35
+ #
36
+ # Examples:
37
+ # mock.stubs.bang!
38
+ # mock.stubs.ping.as { :pong }
39
+ # mock.stubs.hello?(/World/).as { true } # Respond with +true+ if called with a parameter for which <tt>/World/ === param</tt> is true
40
+ def stubs
41
+ CallRecorder.new(method(:stub_call))
42
+ end
43
+
44
+ # Tell the mock to expect a call, optionally with specific parameters.
45
+ # If the call has not been made when #verify is called, #verify will fail
46
+ # with a Test::Unit::AssertionFailed.
47
+ #
48
+ # Parameters to the expected call will be used to match the actual parameters
49
+ # passed by client code later. The match (===) method of the expectation
50
+ # parameter is used to determine whether the client's call matched this anticipated
51
+ # call.
52
+ #
53
+ # Examples:
54
+ # mock.expects.bang!
55
+ # mock.expects.ping.as { :pong }
56
+ # mock.expects.safe?(1..10).as { true } # Expect a call with a parameter for which <tt>(1..10) === param</tt>
57
+ def expects
58
+ CallRecorder.new(method(:expect_call))
59
+ end
60
+
61
+ # Check that the expected calls to this mock were made, and
62
+ # raise a Test::Unit::AssertionFailed exception otherwise. This
63
+ # method will be called automatically if you use the methods provided
64
+ # by TestCaseHelpers
65
+ def verify
66
+ missing_expectations = @expectations - @satisfied_expectations
67
+ if missing_expectations.any?
68
+ raise AssertionFailedError.new("Expected #{missing_expectations[0]} did not happen")
69
+ end
70
+ end
71
+
72
+ # Execute the given block and call #verify afterwards. You are unlikely
73
+ # to use this method, since the methods in TestCaseHelpers render it
74
+ # somewhat redundant.
75
+ def use &block
76
+ block.call(proxy)
77
+ verify
78
+ end
79
+
80
+ private
81
+
82
+ def create_proxy
83
+ @proxy = Object.new
84
+ @stubs = @proxy.instance_eval '@stubs = {}'
85
+ end
86
+
87
+ def stub_for(method_name)
88
+ mock_method = @stubs[method_name]
89
+ unless mock_method
90
+ mock_method = @stubs[method_name]= Response.new
91
+ install_method_in_proxy(method_name)
92
+ end
93
+ mock_method
94
+ end
95
+
96
+ def stub_call(method_name, *argspec)
97
+ stub_for(method_name).add_handler(CallMatcher.new(method_name, argspec))
98
+ end
99
+
100
+ def expect_call(method_name, *argspec)
101
+ handler = CallMatcher.new(method_name, argspec) do |satisfied|
102
+ if @satisfied_expectations.include?(satisfied)
103
+ raise AssertionFailedError.new("Unexpected extra call to #{method_name}")
104
+ end
105
+ @satisfied_expectations << satisfied
106
+ end
107
+ @expectations << stub_for(method_name).add_handler(handler)
108
+ handler
109
+ end
110
+
111
+ def install_method_in_proxy(method_name)
112
+ @proxy.instance_eval(<<-EOF)
113
+ def #{method_name.to_s}(*args, &block)
114
+ args << block if block
115
+ @stubs[:#{method_name}].call(*args)
116
+ end
117
+ EOF
118
+ end
119
+ end
120
+
121
+ ################################################################################
122
+ private
123
+ ################################################################################
124
+
125
+ class CallMatcher # :nodoc:
126
+
127
+ def initialize(method_name, argspec, &listener)
128
+ @method_name = method_name
129
+ @argspec = argspec
130
+ @response = lambda { }
131
+ @listener = listener
132
+ end
133
+
134
+ def ===(args)
135
+ return false if args.size > @argspec.size
136
+ @argspec.zip(args).each do |expected, actual|
137
+ return false unless expected === actual
138
+ end
139
+ true
140
+ end
141
+
142
+ def call
143
+ @listener.call(self) if @listener
144
+ @response.call
145
+ end
146
+
147
+ def to_s
148
+ "call to #{@method_name} with args #{@argspec.inspect}"
149
+ end
150
+
151
+ def as(&block)
152
+ @response = block
153
+ end
154
+ end
155
+
156
+ ################################################################################
157
+
158
+ class Response # :nodoc:
159
+ include ::Test::Unit
160
+
161
+ def initialize
162
+ @handlers = []
163
+ end
164
+
165
+ def add_handler(handler)
166
+ @handlers << handler; handler
167
+ end
168
+
169
+ def call(*args, &block)
170
+ args << block if block
171
+ @handlers.each do |handler|
172
+ return handler.call if handler === args
173
+ end
174
+ raise AssertionFailedError.new("no match for arguments: #{args.inspect}")
175
+ end
176
+ end
177
+
178
+ class CallRecorder # :nodoc:
179
+ def initialize(on_call)
180
+ @on_call = on_call
181
+ end
182
+ public_instance_methods.each do |meth|
183
+ undef_method(meth) unless %w(__id__ __send__ method_missing).include?(meth)
184
+ end
185
+ def method_missing(meth, *args)
186
+ @on_call.call(meth, *args)
187
+ end
188
+ end
189
+
190
+ # Include this module in your Test::Unit::TestCase in order to more conveniently
191
+ # use and verify mocks.
192
+ module TestCaseHelpers
193
+
194
+ # Create a new mock and remember it so that it can be automatically
195
+ # verified when the test case completes
196
+ def new_mock
197
+ @mocks ||= []
198
+ @mocks << (mock = Mock.new)
199
+ mock
200
+ end
201
+
202
+ alias mock new_mock
203
+
204
+ def teardown # :nodoc:
205
+ verify_all_mocks
206
+ end
207
+
208
+ # Verify all mocks that were created using #new_mock.
209
+ #
210
+ # Usually you will not
211
+ # need to call this method yourself -- it is called automatically unless
212
+ # you override #teardown for your own purposes
213
+ def verify_all_mocks
214
+ return unless instance_variables.include?("@mocks")
215
+ @mocks.each do |mock|
216
+ mock.verify
217
+ end
218
+ end
219
+
220
+ end
221
+
222
+ end
@@ -0,0 +1,165 @@
1
+ #!/usr/bin/env ruby
2
+ $VERBOSE = true if $0 == __FILE__
3
+ $:.unshift(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require 'test/unit'
6
+ require 'mockr'
7
+
8
+
9
+ module Mockr::Test
10
+
11
+ class MockTest < Test::Unit::TestCase
12
+ include ::Test::Unit
13
+ include Mockr
14
+
15
+ def test_stub_call_with_no_arguments_returns_nil
16
+ mock = Mock.new
17
+ mock.stubs.ping
18
+ assert_nil mock.proxy.ping
19
+ end
20
+
21
+ def test_stub_call_with_no_arguments_will_return_preset_value
22
+ mock = Mock.new
23
+ mock.stubs.ping.as {'pong'}
24
+ assert_equal 'pong', mock.proxy.ping
25
+ end
26
+
27
+ def test_can_construct_mock_with_a_block
28
+ mock = Mock.new do |m|
29
+ m.stubs.ping.as {'pong'}
30
+ end
31
+ assert_equal 'pong', mock.proxy.ping
32
+ end
33
+
34
+ def test_error_raised_if_missing_mock_method_invoked
35
+ proxy = Mock.new.proxy
36
+ assert_raise NoMethodError do proxy.ping end
37
+ end
38
+
39
+ def test_verify_after_calling_no_stubs_does_nothing
40
+ Mock.new.verify
41
+ Mock.new { |m| m.stubs.ping }.verify
42
+ end
43
+
44
+ def test_verify_after_calling_stubs_does_nothing
45
+ mock = Mock.new do |m| m.stubs.ping end
46
+ mock.proxy.ping
47
+ mock.verify
48
+ end
49
+
50
+ def test_can_expect_and_call_method_with_no_arguments
51
+ mock = Mock.new do |m| m.expects.bing end
52
+ assert_nil mock.proxy.bing
53
+ end
54
+
55
+ def test_verify_after_calling_expected_method_with_no_args
56
+ mock = Mock.new do |m| m.expects.bing end
57
+ mock.proxy.bing
58
+ mock.verify
59
+ end
60
+
61
+ def test_verify_fails_when_expected_method_not_called
62
+ mock = Mock.new do |m| m.expects.bing end
63
+ assert_raise AssertionFailedError do mock.verify end
64
+ end
65
+
66
+ def test_can_use_mock_with_block_in_order_to_have_verify_called_automatically
67
+ mock = Mock.new do |m|
68
+ m.expects.some_method
69
+ m.expects.some_other_method
70
+ end
71
+ assert_raise AssertionFailedError do
72
+ mock.use { |o| o.some_method }
73
+ end
74
+ end
75
+
76
+ def test_can_call_a_stub_twice
77
+ mock = Mock.new do |m| m.stubs.bing.as {'crosby'} end
78
+ 2.times do assert_equal 'crosby', mock.proxy.bing end
79
+ end
80
+
81
+ def test_can_stub_a_method_with_an_argument
82
+ mock = Mock.new do |m| m.stubs.bing('who?').as {'crosby'} end
83
+ assert_equal 'crosby', mock.proxy.bing('who?')
84
+ end
85
+
86
+ def test_can_stub_a_method_twice_with_different_args
87
+ mock = Mock.new do |m|
88
+ m.stubs.last_name?('marlon').as {'brando'}
89
+ m.stubs.last_name?('jimmy').as {'cagney'}
90
+ end
91
+ assert_equal 'cagney', mock.proxy.last_name?('jimmy')
92
+ assert_equal 'brando', mock.proxy.last_name?('marlon')
93
+ end
94
+
95
+ def test_can_expect_a_method_with_an_argument
96
+ mock = Mock.new
97
+ mock.expects.who_am_i?('Jackie').as { 'Chan' }
98
+ mock.use do |o|
99
+ assert_equal 'Chan', o.who_am_i?('Jackie')
100
+ end
101
+ end
102
+
103
+ def test_error_raised_if_expected_method_called_twice
104
+ mock = Mock.new do |m| m.expects.some_method end
105
+ mock.proxy.some_method
106
+ assert_raise AssertionFailedError do mock.proxy.some_method end
107
+ end
108
+
109
+ def test_can_stub_pre_existing_methods
110
+ mock = Mock.new do |m| m.stubs.to_s.as {'foobar'} end
111
+ assert_equal 'foobar', mock.proxy.to_s
112
+ end
113
+
114
+ def test_can_stub_call_that_expects_a_block
115
+ mock = Mock.new do |m| m.stubs.each(Proc) end
116
+ mock.proxy.each {}
117
+ end
118
+
119
+ def test_error_raised_if_block_not_given_to_stubbed_method_wanting_a_block
120
+ mock = Mock.new do |m| m.stubs.each(Proc) end
121
+ assert_raise AssertionFailedError do
122
+ mock.proxy.each
123
+ end
124
+ end
125
+
126
+ def test_can_use_ranges_to_specify_argument_matches
127
+ mock = Mock.new do |m|
128
+ m.stubs.between_1_and_5?(1..5).as { true }
129
+ m.stubs.between_1_and_5?(6..10).as { false }
130
+ end
131
+ mock.use do |o|
132
+ assert_equal true, o.between_1_and_5?(3)
133
+ assert_equal false, o.between_1_and_5?(7)
134
+ end
135
+ end
136
+
137
+ def test_can_stub_constants
138
+ mock = Mock.new do |m| m.stubs.FOOBAR.as { "jimini" } end
139
+ assert_equal 'jimini', mock.proxy.FOOBAR
140
+ end
141
+
142
+ def test_can_expect_constants
143
+ mock = Mock.new do |m| m.expects.FOOBAR.as { "jimini" } end
144
+ assert_equal 'jimini', mock.proxy.FOOBAR
145
+ end
146
+
147
+ end
148
+
149
+ # class AutoMockTest < Test::Unit::TestCase
150
+ # include RMock::TestCaseHelpers
151
+ #
152
+ # def test_foo
153
+ # m = new_mock
154
+ # m.expects.foo.as { 'bar' }
155
+ # end
156
+ #
157
+ # end
158
+
159
+ # TODO:
160
+ # * Clearer error messages
161
+ # * Namespaces for constants
162
+ # * Intercepting top-level calls such as File#open
163
+ # * Explicit test for use of === in arg matching
164
+ # * Allow specification of call order
165
+ end
metadata ADDED
@@ -0,0 +1,47 @@
1
+ --- !ruby/object:Gem::Specification
2
+ rubygems_version: 0.9.0
3
+ specification_version: 1
4
+ name: mockr
5
+ version: !ruby/object:Gem::Version
6
+ version: "0.1"
7
+ date: 2006-07-31 00:00:00 +02:00
8
+ summary: Easy Mock Objects for Ruby.
9
+ require_paths:
10
+ - lib
11
+ email:
12
+ homepage:
13
+ rubyforge_project:
14
+ description: "MockR is a tiny mock object library inspired by JMock. Main selling points: * Natural and unintrusive syntax * Supports the distinction between mocks and stubs * Constraint-based mechanism for matching call parameters See http://mockr.sanityinc.com/ for more info."
15
+ autorequire:
16
+ default_executable:
17
+ bindir: bin
18
+ has_rdoc: true
19
+ required_ruby_version: !ruby/object:Gem::Version::Requirement
20
+ requirements:
21
+ - - ">"
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.0
24
+ version:
25
+ platform: ruby
26
+ signing_key:
27
+ cert_chain:
28
+ post_install_message:
29
+ authors: []
30
+
31
+ files:
32
+ - lib/mockr.rb
33
+ - test/mockr_tests.rb
34
+ test_files: []
35
+
36
+ rdoc_options: []
37
+
38
+ extra_rdoc_files: []
39
+
40
+ executables: []
41
+
42
+ extensions: []
43
+
44
+ requirements:
45
+ - none
46
+ dependencies: []
47
+