mockr 0.1
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/lib/mockr.rb +222 -0
- data/test/mockr_tests.rb +165 -0
- 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
|
data/test/mockr_tests.rb
ADDED
@@ -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
|
+
|