hardmock 1.2.3 → 1.3.0
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/Rakefile +1 -1
- data/lib/assert_error.rb +17 -15
- data/lib/hardmock/errors.rb +3 -0
- data/lib/hardmock/expectation.rb +1 -1
- data/lib/hardmock/mock_control.rb +10 -2
- data/lib/hardmock/stubbing.rb +174 -0
- data/lib/hardmock.rb +29 -2
- data/test/functional/assert_error_test.rb +6 -6
- data/test/functional/auto_verify_test.rb +10 -11
- data/test/functional/direct_mock_usage_test.rb +28 -28
- data/test/functional/hardmock_test.rb +50 -60
- data/test/functional/stubbing_test.rb +274 -0
- data/test/test_helper.rb +23 -0
- data/test/unit/expectation_test.rb +11 -0
- metadata +5 -2
data/Rakefile
CHANGED
data/lib/assert_error.rb
CHANGED
@@ -1,20 +1,22 @@
|
|
1
1
|
require 'test/unit/assertions'
|
2
2
|
|
3
|
-
module Test::Unit
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
3
|
+
module Test::Unit #:nodoc:#
|
4
|
+
module Assertions #:nodoc:#
|
5
|
+
# A better 'assert_raise'. +patterns+ can be one or more Regexps, or a literal String that
|
6
|
+
# must match the entire error message.
|
7
|
+
def assert_error(err_type,*patterns,&block)
|
8
|
+
assert_not_nil block, "assert_error requires a block"
|
9
|
+
assert((err_type and err_type.kind_of?(Class)), "First argument to assert_error has to be an error type")
|
10
|
+
err = assert_raise(err_type) do
|
11
|
+
block.call
|
12
|
+
end
|
13
|
+
patterns.each do |pattern|
|
14
|
+
case pattern
|
15
|
+
when Regexp
|
16
|
+
assert_match(pattern, err.message)
|
17
|
+
else
|
18
|
+
assert_equal pattern, err.message
|
19
|
+
end
|
18
20
|
end
|
19
21
|
end
|
20
22
|
end
|
data/lib/hardmock/errors.rb
CHANGED
@@ -7,6 +7,9 @@ module Hardmock
|
|
7
7
|
# Raised for methods that should no longer be called. Hopefully, the exception message contains helpful alternatives.
|
8
8
|
class DeprecationError < StandardError; end
|
9
9
|
|
10
|
+
# Raised when stubbing fails
|
11
|
+
class StubbingError < StandardError; end
|
12
|
+
|
10
13
|
# Raised when it is discovered that an expected method call was never made.
|
11
14
|
class VerifyError < StandardError
|
12
15
|
def initialize(msg,unmet_expectations)
|
data/lib/hardmock/expectation.rb
CHANGED
@@ -176,7 +176,7 @@ module Hardmock
|
|
176
176
|
# Yield once
|
177
177
|
@options[:block] = lambda do |block|
|
178
178
|
if block.arity != 0 and block.arity != -1
|
179
|
-
raise ExpectationError.new("
|
179
|
+
raise ExpectationError.new("The given block was expected to have no parameter count; instead, got #{block.arity} to <#{to_s}>")
|
180
180
|
end
|
181
181
|
block.call
|
182
182
|
end
|
@@ -6,8 +6,7 @@ module Hardmock
|
|
6
6
|
attr_accessor :name
|
7
7
|
|
8
8
|
def initialize
|
9
|
-
|
10
|
-
@disappointed = false
|
9
|
+
clear_expectations
|
11
10
|
end
|
12
11
|
|
13
12
|
def happy?
|
@@ -19,6 +18,7 @@ module Hardmock
|
|
19
18
|
end
|
20
19
|
|
21
20
|
def add_expectation(expectation)
|
21
|
+
# puts "MockControl #{self.object_id.to_s(16)} adding expectation: #{expectation}"
|
22
22
|
@expectations << expectation
|
23
23
|
end
|
24
24
|
|
@@ -38,8 +38,16 @@ module Hardmock
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def verify
|
41
|
+
# puts "MockControl #{self.object_id.to_s(16)} verify: happy? #{happy?}"
|
41
42
|
@disappointed = !happy?
|
42
43
|
raise VerifyError.new("Unmet expectations", @expectations) unless happy?
|
43
44
|
end
|
45
|
+
|
46
|
+
def clear_expectations
|
47
|
+
@expectations = []
|
48
|
+
@disappointed = false
|
49
|
+
end
|
50
|
+
|
44
51
|
end
|
52
|
+
|
45
53
|
end
|
@@ -0,0 +1,174 @@
|
|
1
|
+
#
|
2
|
+
# Stubbing support
|
3
|
+
#
|
4
|
+
# Stubs methods on classes and instances
|
5
|
+
#
|
6
|
+
|
7
|
+
# Why's "metaid.rb":
|
8
|
+
class Object #:nodoc:#
|
9
|
+
# The hidden singleton lurks behind everyone
|
10
|
+
def metaclass #:nodoc:#
|
11
|
+
class << self
|
12
|
+
self
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Evaluate a block of code within the metaclass
|
17
|
+
def meta_eval(&blk) #:nodoc:#
|
18
|
+
metaclass.instance_eval(&blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Adds methods to a metaclass
|
22
|
+
def meta_def(name, &blk) #:nodoc:#
|
23
|
+
meta_eval { define_method name, &blk }
|
24
|
+
end
|
25
|
+
|
26
|
+
# Defines an instance method within a class
|
27
|
+
# def class_def(name, &blk) #:nodoc:#
|
28
|
+
# class_eval { define_method name, &blk }
|
29
|
+
# end
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
|
34
|
+
module Hardmock
|
35
|
+
|
36
|
+
# == Hardmock: Stubbing and Mocking Concrete Methods
|
37
|
+
#
|
38
|
+
# Hardmock lets you stub and/or mock methods on concrete classes or objects.
|
39
|
+
#
|
40
|
+
# * To "stub" a concrete method is to rig it to return the same thing always, disregarding any arguments.
|
41
|
+
# * To "mock" a concrete method is to surplant its funcionality by delegating to a mock object who will cover this behavior.
|
42
|
+
#
|
43
|
+
# Mocked methods have their expectations considered along with all other mock object expectations.
|
44
|
+
#
|
45
|
+
# If you use stubbing or concrete mocking in the absence (or before creation) of other mocks, you need to invoke <tt>prepare_hardmock_control</tt>.
|
46
|
+
# Once <tt>verify_mocks</tt> or <tt>clear_expectaions</tt> is called, the overriden behavior in the target objects is restored.
|
47
|
+
#
|
48
|
+
# == Examples
|
49
|
+
#
|
50
|
+
# River.stubs!(:sounds_like).returns("gurgle")
|
51
|
+
#
|
52
|
+
# River.expects!(:jump).returns("splash")
|
53
|
+
#
|
54
|
+
# rogue.stubs!(:sounds_like).returns("pshshsh")
|
55
|
+
#
|
56
|
+
# rogue.expects!(:rawhide_tanning_solvents).returns("giant snapping turtles")
|
57
|
+
#
|
58
|
+
module Stubbing
|
59
|
+
# Exists only for documentation
|
60
|
+
end
|
61
|
+
|
62
|
+
class StubbedMethod #:nodoc:#
|
63
|
+
attr_reader :target, :method_name
|
64
|
+
|
65
|
+
def initialize(target, method_name)
|
66
|
+
@target = target
|
67
|
+
@method_name = method_name
|
68
|
+
|
69
|
+
Hardmock.add_stubbed_method self
|
70
|
+
end
|
71
|
+
|
72
|
+
def invoke(args)
|
73
|
+
@return_value
|
74
|
+
end
|
75
|
+
|
76
|
+
def returns(stubbed_return)
|
77
|
+
@return_value = stubbed_return
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class MockedMethod < StubbedMethod #:nodoc:#
|
82
|
+
|
83
|
+
def initialize(target, method_name, mock)
|
84
|
+
super target,method_name
|
85
|
+
@mock = mock
|
86
|
+
end
|
87
|
+
|
88
|
+
def invoke(args)
|
89
|
+
@mock.__send__(self.method_name.to_sym, *args)
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
class ::Object
|
95
|
+
def stubs!(method_name)
|
96
|
+
_ensure_stubbable method_name
|
97
|
+
|
98
|
+
method_name = method_name.to_s
|
99
|
+
stubbed_method = Hardmock::StubbedMethod.new(self, method_name)
|
100
|
+
|
101
|
+
meta_eval do
|
102
|
+
alias_method "_hardmock_original_#{method_name}".to_sym, method_name.to_sym
|
103
|
+
end
|
104
|
+
|
105
|
+
meta_def method_name do |*args|
|
106
|
+
stubbed_method.invoke(args)
|
107
|
+
end
|
108
|
+
|
109
|
+
stubbed_method
|
110
|
+
end
|
111
|
+
|
112
|
+
def expects!(method_name, *args, &block)
|
113
|
+
_ensure_stubbable method_name
|
114
|
+
|
115
|
+
method_name = method_name.to_s
|
116
|
+
|
117
|
+
if @_my_mock.nil?
|
118
|
+
@_my_mock = Mock.new(_my_name, $main_mock_control)
|
119
|
+
stubbed_method = Hardmock::MockedMethod.new(self, method_name, @_my_mock)
|
120
|
+
meta_eval do
|
121
|
+
alias_method "_hardmock_original_#{method_name}".to_sym, method_name.to_sym
|
122
|
+
end
|
123
|
+
meta_def(method_name) do |*args|
|
124
|
+
stubbed_method.invoke(args)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
return @_my_mock.expects(method_name, *args, &block)
|
129
|
+
end
|
130
|
+
|
131
|
+
def _ensure_stubbable(method_name)
|
132
|
+
unless self.respond_to?(method_name.to_sym)
|
133
|
+
msg = "Cannot stub non-existant "
|
134
|
+
if self.kind_of?(Class)
|
135
|
+
msg += "class method #{_my_name}."
|
136
|
+
else
|
137
|
+
msg += "method #{_my_name}#"
|
138
|
+
end
|
139
|
+
msg += method_name.to_s
|
140
|
+
raise Hardmock::StubbingError.new(msg)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def _my_name
|
145
|
+
self.kind_of?(Class) ? self.name : self.class.name
|
146
|
+
end
|
147
|
+
|
148
|
+
def _clear_mock
|
149
|
+
@_my_mock = nil
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
|
154
|
+
class << self
|
155
|
+
def add_stubbed_method(stubbed_method)
|
156
|
+
all_stubbed_methods << stubbed_method
|
157
|
+
end
|
158
|
+
|
159
|
+
def all_stubbed_methods
|
160
|
+
$all_stubbed_methods ||= []
|
161
|
+
end
|
162
|
+
|
163
|
+
def restore_all_stubbed_methods
|
164
|
+
all_stubbed_methods.each do |sm|
|
165
|
+
sm.target.meta_eval do
|
166
|
+
alias_method sm.method_name.to_sym, "_hardmock_original_#{sm.method_name}".to_sym
|
167
|
+
end
|
168
|
+
sm.target._clear_mock
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
data/lib/hardmock.rb
CHANGED
@@ -7,6 +7,7 @@ require 'hardmock/trapper'
|
|
7
7
|
require 'hardmock/expector'
|
8
8
|
require 'hardmock/expectation'
|
9
9
|
require 'hardmock/expectation_builder'
|
10
|
+
require 'hardmock/stubbing'
|
10
11
|
|
11
12
|
module Hardmock
|
12
13
|
|
@@ -73,7 +74,7 @@ module Hardmock
|
|
73
74
|
# For more info on how to use your mocks, see Mock and Expectation
|
74
75
|
#
|
75
76
|
def create_mocks(*mock_names)
|
76
|
-
@main_mock_control
|
77
|
+
prepare_hardmock_control unless @main_mock_control
|
77
78
|
|
78
79
|
mocks = {}
|
79
80
|
mock_names.each do |mock_name|
|
@@ -89,6 +90,15 @@ module Hardmock
|
|
89
90
|
return mocks.clone
|
90
91
|
end
|
91
92
|
|
93
|
+
def prepare_hardmock_control
|
94
|
+
if @main_mock_control.nil?
|
95
|
+
@main_mock_control = MockControl.new
|
96
|
+
$main_mock_control = @main_mock_control
|
97
|
+
else
|
98
|
+
raise "@main_mock_control is already setup for this test!"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
92
102
|
alias :create_mock :create_mocks
|
93
103
|
|
94
104
|
# Ensures that all expectations have been met. If not, VerifyException is
|
@@ -101,17 +111,34 @@ module Hardmock
|
|
101
111
|
return unless @main_mock_control
|
102
112
|
return if @main_mock_control.disappointed? and !force
|
103
113
|
@main_mock_control.verify
|
114
|
+
ensure
|
115
|
+
@main_mock_control.clear_expectations if @main_mock_control
|
116
|
+
Hardmock.restore_all_stubbed_methods
|
117
|
+
$main_mock_control = nil
|
104
118
|
end
|
105
119
|
|
120
|
+
# Purge the main MockControl of all expectations, restore all concrete stubbed/mocked methods
|
121
|
+
def clear_expectations
|
122
|
+
@main_mock_control.clear_expectations if @main_mock_control
|
123
|
+
Hardmock.restore_all_stubbed_methods
|
124
|
+
$main_mock_control = nil
|
125
|
+
end
|
106
126
|
|
127
|
+
# def self.set_main_mock_control(control)
|
128
|
+
# $main_mock_control = control
|
129
|
+
# end
|
107
130
|
|
131
|
+
# def self.main_mock_control
|
132
|
+
# raise "No main mock control set yet... chickening out" if $main_mock_control.nil?
|
133
|
+
# $main_mock_control
|
134
|
+
# end
|
108
135
|
|
109
136
|
end
|
110
137
|
|
111
138
|
# Insert Hardmock functionality into the TestCase base class
|
112
139
|
require 'test/unit/testcase'
|
113
140
|
unless Test::Unit::TestCase.instance_methods.include?('hardmock_teardown')
|
114
|
-
class Test::Unit::TestCase
|
141
|
+
class Test::Unit::TestCase #:nodoc:#
|
115
142
|
include Hardmock
|
116
143
|
end
|
117
144
|
end
|
@@ -3,13 +3,13 @@ require 'assert_error'
|
|
3
3
|
|
4
4
|
class AssertErrorTest < Test::Unit::TestCase
|
5
5
|
|
6
|
-
|
6
|
+
it "specfies an error type and message that should be raised" do
|
7
7
|
assert_error RuntimeError, "Too funky" do
|
8
8
|
raise RuntimeError.new("Too funky")
|
9
9
|
end
|
10
10
|
end
|
11
11
|
|
12
|
-
|
12
|
+
it "flunks if the error message is wrong" do
|
13
13
|
err = assert_raise Test::Unit::AssertionFailedError do
|
14
14
|
assert_error RuntimeError, "not good" do
|
15
15
|
raise RuntimeError.new("Too funky")
|
@@ -19,7 +19,7 @@ class AssertErrorTest < Test::Unit::TestCase
|
|
19
19
|
assert_match(/too funky/i, err.message)
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
it "flunks if the error type is wrong" do
|
23
23
|
err = assert_raise Test::Unit::AssertionFailedError do
|
24
24
|
assert_error StandardError, "Too funky" do
|
25
25
|
raise RuntimeError.new("Too funky")
|
@@ -29,13 +29,13 @@ class AssertErrorTest < Test::Unit::TestCase
|
|
29
29
|
assert_match(/RuntimeError/i, err.message)
|
30
30
|
end
|
31
31
|
|
32
|
-
|
32
|
+
it "can match error message text using a series of Regexps" do
|
33
33
|
assert_error StandardError, /too/i, /funky/i do
|
34
34
|
raise StandardError.new("Too funky")
|
35
35
|
end
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
it "flunks if the error message doesn't match all the Regexps" do
|
39
39
|
err = assert_raise Test::Unit::AssertionFailedError do
|
40
40
|
assert_error StandardError, /way/i, /too/i, /funky/i do
|
41
41
|
raise StandardError.new("Too funky")
|
@@ -44,7 +44,7 @@ class AssertErrorTest < Test::Unit::TestCase
|
|
44
44
|
assert_match(/way/i, err.message)
|
45
45
|
end
|
46
46
|
|
47
|
-
|
47
|
+
it "can operate without any message specification" do
|
48
48
|
assert_error StandardError do
|
49
49
|
raise StandardError.new("ooof")
|
50
50
|
end
|
@@ -15,11 +15,11 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
15
15
|
# TESTS
|
16
16
|
#
|
17
17
|
|
18
|
-
|
18
|
+
it "auto-verifies all mocks in teardown" do
|
19
19
|
write_and_execute_test
|
20
20
|
end
|
21
21
|
|
22
|
-
|
22
|
+
it "auto-verifies even if user defines own teardown" do
|
23
23
|
@teardown_code =<<-EOM
|
24
24
|
def teardown
|
25
25
|
# just in the way
|
@@ -28,7 +28,7 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
28
28
|
write_and_execute_test
|
29
29
|
end
|
30
30
|
|
31
|
-
|
31
|
+
should "not obscure normal failures when verification fails" do
|
32
32
|
@test_code =<<-EOM
|
33
33
|
def test_setup_doomed_expectation
|
34
34
|
create_mock :automobile
|
@@ -40,7 +40,7 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
40
40
|
write_and_execute_test
|
41
41
|
end
|
42
42
|
|
43
|
-
|
43
|
+
should "not skip user-defined teardown when verification fails" do
|
44
44
|
@teardown_code =<<-EOM
|
45
45
|
def teardown
|
46
46
|
puts "User teardown"
|
@@ -50,7 +50,8 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
50
50
|
assert_output_contains(/User teardown/)
|
51
51
|
end
|
52
52
|
|
53
|
-
|
53
|
+
it "is quiet when verification is ok" do
|
54
|
+
# def test_should_not_raise_error_if_verification_goes_according_to_plan
|
54
55
|
@test_code =<<-EOM
|
55
56
|
def test_ok
|
56
57
|
create_mock :automobile
|
@@ -70,7 +71,8 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
70
71
|
assert_output_contains(/User teardown/)
|
71
72
|
end
|
72
73
|
|
73
|
-
|
74
|
+
should "not auto-verify if user teardown explodes" do
|
75
|
+
# def test_should_not_do_verification_if_user_teardown_explodes
|
74
76
|
@teardown_code =<<-EOM
|
75
77
|
def teardown
|
76
78
|
raise "self destruct"
|
@@ -81,7 +83,7 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
81
83
|
assert_output_contains(/self destruct/)
|
82
84
|
end
|
83
85
|
|
84
|
-
|
86
|
+
it "plays nice with inherited teardown methods" do
|
85
87
|
@full_code ||=<<-EOTEST
|
86
88
|
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
87
89
|
require 'hardmock'
|
@@ -101,7 +103,7 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
101
103
|
assert_output_contains(/Test helper teardown/)
|
102
104
|
end
|
103
105
|
|
104
|
-
|
106
|
+
it "plays nice with inherited and user-defined teardowns at the same time" do
|
105
107
|
@full_code ||=<<-EOTEST
|
106
108
|
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
107
109
|
class Test::Unit::TestCase
|
@@ -135,9 +137,6 @@ class AutoVerifyTest < Test::Unit::TestCase
|
|
135
137
|
def run_test(tbody)
|
136
138
|
File.open(temp_test_file,"w") { |f| f.print(tbody) }
|
137
139
|
@test_output = `ruby #{temp_test_file} 2>&1`
|
138
|
-
# puts "------------------------"
|
139
|
-
# puts @test_output
|
140
|
-
# puts "------------------------"
|
141
140
|
end
|
142
141
|
|
143
142
|
def remove_temp_test_file
|
@@ -14,7 +14,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
14
14
|
# TESTS
|
15
15
|
#
|
16
16
|
|
17
|
-
|
17
|
+
it "raises VerifyError if expected method not called" do
|
18
18
|
@bird.expects.flap_flap
|
19
19
|
|
20
20
|
err = assert_raise VerifyError do
|
@@ -23,7 +23,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
23
23
|
assert_match(/unmet expectations/i, err.message)
|
24
24
|
end
|
25
25
|
|
26
|
-
|
26
|
+
should "not raise when expected calls are made in order" do
|
27
27
|
@bird.expects.flap_flap
|
28
28
|
@bird.expects.bang
|
29
29
|
@bird.expects.plop
|
@@ -35,7 +35,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
35
35
|
@bird._verify
|
36
36
|
end
|
37
37
|
|
38
|
-
|
38
|
+
it "raises ExpectationError when unexpected method are called" do
|
39
39
|
@bird.expects.flap_flap
|
40
40
|
|
41
41
|
err = assert_raise ExpectationError do
|
@@ -44,7 +44,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
44
44
|
assert_match(/wrong method/i, err.message)
|
45
45
|
end
|
46
46
|
|
47
|
-
|
47
|
+
it "raises ExpectationError on bad arguments" do
|
48
48
|
@bird.expects.flap_flap(:swoosh)
|
49
49
|
|
50
50
|
err = assert_raise ExpectationError do
|
@@ -53,7 +53,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
53
53
|
assert_match(/wrong arguments/i, err.message)
|
54
54
|
end
|
55
55
|
|
56
|
-
|
56
|
+
it "raises VerifyError when not all expected methods are called" do
|
57
57
|
@bird.expects.flap_flap
|
58
58
|
@bird.expects.bang
|
59
59
|
@bird.expects.plop
|
@@ -66,7 +66,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
66
66
|
assert_match(/unmet expectations/i, err.message)
|
67
67
|
end
|
68
68
|
|
69
|
-
|
69
|
+
it "raises ExpectationError when calls are made out of order" do
|
70
70
|
@bird.expects.flap_flap
|
71
71
|
@bird.expects.bang
|
72
72
|
@bird.expects.plop
|
@@ -78,7 +78,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
78
78
|
assert_match(/wrong method/i, err.message)
|
79
79
|
end
|
80
80
|
|
81
|
-
|
81
|
+
it "returns the configured value" do
|
82
82
|
@bird.expects.plop.returns(':P')
|
83
83
|
assert_equal ':P', @bird.plop
|
84
84
|
@bird._verify
|
@@ -88,13 +88,13 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
88
88
|
@bird._verify
|
89
89
|
end
|
90
90
|
|
91
|
-
|
91
|
+
it "returns nil when no return is specified" do
|
92
92
|
@bird.expects.plop
|
93
93
|
assert_nil @bird.plop
|
94
94
|
@bird._verify
|
95
95
|
end
|
96
96
|
|
97
|
-
|
97
|
+
it "raises the configured exception" do
|
98
98
|
err = RuntimeError.new('shaq')
|
99
99
|
@bird.expects.plop.raises(err)
|
100
100
|
actual_err = assert_raise RuntimeError do
|
@@ -104,7 +104,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
104
104
|
@bird._verify
|
105
105
|
end
|
106
106
|
|
107
|
-
|
107
|
+
it "raises a RuntimeError when told to 'raise' a string" do
|
108
108
|
@bird.expects.plop.raises('shaq')
|
109
109
|
err = assert_raise RuntimeError do
|
110
110
|
@bird.plop
|
@@ -113,7 +113,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
113
113
|
@bird._verify
|
114
114
|
end
|
115
115
|
|
116
|
-
|
116
|
+
it "raises a default RuntimeError" do
|
117
117
|
@bird.expects.plop.raises
|
118
118
|
err = assert_raise RuntimeError do
|
119
119
|
@bird.plop
|
@@ -122,14 +122,14 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
122
122
|
@bird._verify
|
123
123
|
end
|
124
124
|
|
125
|
-
|
125
|
+
it "is quiet when correct arguments given" do
|
126
126
|
thing = Object.new
|
127
127
|
@bird.expects.plop(:big,'one',thing)
|
128
128
|
@bird.plop(:big,'one',thing)
|
129
129
|
@bird._verify
|
130
130
|
end
|
131
131
|
|
132
|
-
|
132
|
+
it "raises ExpectationError when wrong number of arguments specified" do
|
133
133
|
thing = Object.new
|
134
134
|
@bird.expects.plop(:big,'one',thing)
|
135
135
|
err = assert_raise ExpectationError do
|
@@ -156,7 +156,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
156
156
|
@bird._verify
|
157
157
|
end
|
158
158
|
|
159
|
-
|
159
|
+
it "raises ExpectationError when arguments don't match" do
|
160
160
|
thing = Object.new
|
161
161
|
@bird.expects.plop(:big,'one',thing)
|
162
162
|
err = assert_raise ExpectationError do
|
@@ -166,7 +166,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
166
166
|
@bird._verify
|
167
167
|
end
|
168
168
|
|
169
|
-
|
169
|
+
it "can use a block for custom reactions" do
|
170
170
|
mitt = nil
|
171
171
|
@bird.expects.plop { mitt = :ball }
|
172
172
|
assert_nil mitt
|
@@ -182,7 +182,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
182
182
|
@bird._verify
|
183
183
|
end
|
184
184
|
|
185
|
-
|
185
|
+
it "passes mock-call arguments to the expectation block" do
|
186
186
|
ball = nil
|
187
187
|
mitt = nil
|
188
188
|
@bird.expects.plop {|arg1,arg2|
|
@@ -197,7 +197,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
197
197
|
@bird._verify
|
198
198
|
end
|
199
199
|
|
200
|
-
|
200
|
+
it "validates arguments if specified in addition to a block" do
|
201
201
|
ball = nil
|
202
202
|
mitt = nil
|
203
203
|
@bird.expects.plop(:ball,:mitt) {|arg1,arg2|
|
@@ -244,7 +244,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
244
244
|
@bird._verify
|
245
245
|
end
|
246
246
|
|
247
|
-
|
247
|
+
it "passes runtime blocks to the expectation block as the final argument" do
|
248
248
|
runtime_block_called = false
|
249
249
|
got_arg = nil
|
250
250
|
|
@@ -271,7 +271,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
271
271
|
@bird._verify
|
272
272
|
end
|
273
273
|
|
274
|
-
|
274
|
+
it "passes the runtime block to the expectation block as sole argument if no other args come into play" do
|
275
275
|
runtime_block_called = false
|
276
276
|
@bird.expects.subscribe { |block| block.call }
|
277
277
|
@bird.subscribe do
|
@@ -280,7 +280,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
280
280
|
assert runtime_block_called, "The runtime block should have been invoked by the user block"
|
281
281
|
end
|
282
282
|
|
283
|
-
|
283
|
+
it "provides nil as final argument if expectation block seems to want a block" do
|
284
284
|
invoked = false
|
285
285
|
@bird.expects.kablam(:scatter) { |shot,block|
|
286
286
|
assert_equal :scatter, shot, "Wrong shot"
|
@@ -293,7 +293,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
293
293
|
@bird._verify
|
294
294
|
end
|
295
295
|
|
296
|
-
|
296
|
+
it "can set explicit return after an expectation block" do
|
297
297
|
got = nil
|
298
298
|
@bird.expects.kablam(:scatter) { |shot|
|
299
299
|
got = shot
|
@@ -305,7 +305,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
305
305
|
@bird._verify
|
306
306
|
end
|
307
307
|
|
308
|
-
|
308
|
+
it "can raise after an expectation block" do
|
309
309
|
got = nil
|
310
310
|
@bird.expects.kablam(:scatter) do |shot|
|
311
311
|
got = shot
|
@@ -319,7 +319,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
319
319
|
@bird._verify
|
320
320
|
end
|
321
321
|
|
322
|
-
|
322
|
+
it "stores the semantic value of the expectation block after it executes" do
|
323
323
|
expectation = @bird.expects.kablam(:slug) { |shot|
|
324
324
|
"The shot was #{shot}"
|
325
325
|
}
|
@@ -336,7 +336,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
336
336
|
end
|
337
337
|
|
338
338
|
|
339
|
-
|
339
|
+
it "uses the value of the expectation block as the default return value" do
|
340
340
|
@bird.expects.kablam(:scatter) { |shot|
|
341
341
|
"The shot was #{shot}"
|
342
342
|
}
|
@@ -345,7 +345,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
345
345
|
@bird._verify
|
346
346
|
end
|
347
347
|
|
348
|
-
|
348
|
+
it "returns the Expectation even if 'returns' is used" do
|
349
349
|
expectation = @bird.expects.kablam(:slug) { |shot|
|
350
350
|
"The shot was #{shot}"
|
351
351
|
}.returns :hosed
|
@@ -361,7 +361,7 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
361
361
|
@bird._verify
|
362
362
|
end
|
363
363
|
|
364
|
-
|
364
|
+
it "returns the Expectation even if 'raises' is used" do
|
365
365
|
expectation = @bird.expects.kablam(:slug) { |shot|
|
366
366
|
"The shot was #{shot}"
|
367
367
|
}.raises "aiee!"
|
@@ -378,13 +378,13 @@ class DirectMockUsageTest < Test::Unit::TestCase
|
|
378
378
|
end
|
379
379
|
|
380
380
|
|
381
|
-
|
381
|
+
it "supports assignment-style methods" do
|
382
382
|
@bird.expects.size = "large"
|
383
383
|
@bird.size = "large"
|
384
384
|
@bird._verify
|
385
385
|
end
|
386
386
|
|
387
|
-
|
387
|
+
it "supports assignments and raising (using explicit-method syntax)" do
|
388
388
|
@bird.expects('size=','large').raises "boom"
|
389
389
|
|
390
390
|
err = assert_raise RuntimeError do
|
@@ -4,33 +4,11 @@ require 'assert_error'
|
|
4
4
|
|
5
5
|
class HardmockTest < Test::Unit::TestCase
|
6
6
|
|
7
|
-
def setup
|
8
|
-
end
|
9
|
-
|
10
|
-
def teardown
|
11
|
-
end
|
12
|
-
|
13
|
-
#
|
14
|
-
# HELPERS
|
15
|
-
#
|
16
|
-
|
17
|
-
def assert_mock_exists(name)
|
18
|
-
assert_not_nil @all_mocks, "@all_mocks not here yet"
|
19
|
-
mo = @all_mocks[name]
|
20
|
-
assert_not_nil mo, "Mock '#{name}' not in @all_mocks"
|
21
|
-
assert_kind_of Mock, mo, "Wrong type of object, wanted a Mock"
|
22
|
-
assert_equal name.to_s, mo._name, "Mock '#{name}' had wrong name"
|
23
|
-
ivar = self.instance_variable_get("@#{name}")
|
24
|
-
assert_not_nil ivar, "Mock '#{name}' not set as ivar"
|
25
|
-
assert_same mo, ivar, "Mock '#{name}' ivar not same as instance in @all_mocks"
|
26
|
-
assert_same @main_mock_control, mo._control, "Mock '#{name}' doesn't share the main mock control"
|
27
|
-
end
|
28
|
-
|
29
7
|
#
|
30
8
|
# TESTS
|
31
9
|
#
|
32
10
|
|
33
|
-
|
11
|
+
it "conveniently creates mocks using create_mock and create_mocks" do
|
34
12
|
assert_nil @main_mock_control, "@main_mock_control not expected yet"
|
35
13
|
|
36
14
|
h = create_mock :donkey
|
@@ -54,7 +32,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
54
32
|
assert_mock_exists :donkey
|
55
33
|
end
|
56
34
|
|
57
|
-
|
35
|
+
it "provides literal 'expects' syntax" do
|
58
36
|
assert_nil @order, "Should be no @order yet"
|
59
37
|
create_mock :order
|
60
38
|
assert_not_nil @order, "@order should be built"
|
@@ -72,7 +50,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
72
50
|
verify_mocks
|
73
51
|
end
|
74
52
|
|
75
|
-
|
53
|
+
it "supports several mocks at once" do
|
76
54
|
create_mocks :order_builder, :order, :customer
|
77
55
|
|
78
56
|
@order_builder.expects.create_new_order.returns @order
|
@@ -88,7 +66,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
88
66
|
verify_mocks
|
89
67
|
end
|
90
68
|
|
91
|
-
|
69
|
+
it "enforces inter-mock call ordering" do
|
92
70
|
create_mocks :order_builder, :order, :customer
|
93
71
|
|
94
72
|
@order_builder.expects.create_new_order.returns @order
|
@@ -108,10 +86,6 @@ class HardmockTest < Test::Unit::TestCase
|
|
108
86
|
assert_error VerifyError, /unmet expectations/i do
|
109
87
|
verify_mocks
|
110
88
|
end
|
111
|
-
|
112
|
-
# Appease the verifier
|
113
|
-
@order.account_no = 1234
|
114
|
-
@order.save!
|
115
89
|
end
|
116
90
|
|
117
91
|
class UserPresenter
|
@@ -127,7 +101,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
127
101
|
end
|
128
102
|
end
|
129
103
|
|
130
|
-
|
104
|
+
it "makes MVP testing simple" do
|
131
105
|
mox = create_mocks :model, :view
|
132
106
|
|
133
107
|
data_change = @model.expects.when(:data_changes) { |evt,block| block }
|
@@ -150,7 +124,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
150
124
|
verify_mocks
|
151
125
|
end
|
152
126
|
|
153
|
-
|
127
|
+
it "continues to function after verify, if verification error is controlled" do
|
154
128
|
mox = create_mocks :model, :view
|
155
129
|
data_change = @model.expects.when(:data_changes) { |evt,block| block }
|
156
130
|
user_edit = @view.expects.when(:user_edited) { |evt,block| block }
|
@@ -174,18 +148,11 @@ class HardmockTest < Test::Unit::TestCase
|
|
174
148
|
verify_mocks(false)
|
175
149
|
end
|
176
150
|
|
177
|
-
# Finish meeting expectations and see good verification behavior
|
178
|
-
@view.user_name = "Da Croz"
|
179
|
-
verify_mocks
|
180
|
-
|
181
151
|
@model.expects.never_gonna_happen
|
182
152
|
|
183
153
|
assert_error VerifyError, /unmet expectations/i, /never_gonna_happen/i do
|
184
154
|
verify_mocks
|
185
155
|
end
|
186
|
-
|
187
|
-
# Appease the verifier
|
188
|
-
@model.never_gonna_happen
|
189
156
|
end
|
190
157
|
|
191
158
|
class UserPresenterBroken
|
@@ -199,7 +166,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
199
166
|
end
|
200
167
|
end
|
201
168
|
|
202
|
-
|
169
|
+
it "flunks for typical Presenter constructor wiring failure" do
|
203
170
|
mox = create_mocks :model, :view
|
204
171
|
|
205
172
|
data_change = @model.expects.when(:data_changes) { |evt,block| block }
|
@@ -213,16 +180,9 @@ class HardmockTest < Test::Unit::TestCase
|
|
213
180
|
assert_match(/unmet expectations/i, err.message)
|
214
181
|
assert_match(/view.when\(:user_edited\)/i, err.message)
|
215
182
|
|
216
|
-
assert_error VerifyError, /unmet expectations/i do
|
217
|
-
verify_mocks
|
218
|
-
end
|
219
|
-
|
220
|
-
# Appease the verifier
|
221
|
-
@view.when(:user_edited)
|
222
|
-
|
223
183
|
end
|
224
184
|
|
225
|
-
|
185
|
+
it "provides convenient event-subscription trap syntax for MVP testing" do
|
226
186
|
mox = create_mocks :model, :view
|
227
187
|
|
228
188
|
data_change = @model.trap.when(:data_changes)
|
@@ -245,6 +205,13 @@ class HardmockTest < Test::Unit::TestCase
|
|
245
205
|
verify_mocks
|
246
206
|
end
|
247
207
|
|
208
|
+
it "raises if you try to pass an expectation block to 'trap'" do
|
209
|
+
create_mock :model
|
210
|
+
assert_error Hardmock::ExpectationError, /blocks/i, /trap/i do
|
211
|
+
@model.trap.when(:some_event) do raise "huh?" end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
248
215
|
class Grinder
|
249
216
|
def initialize(objects)
|
250
217
|
@chute = objects[:chute]
|
@@ -259,7 +226,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
259
226
|
end
|
260
227
|
end
|
261
228
|
|
262
|
-
|
229
|
+
it "lets you write clear iteration-oriented expectations" do
|
263
230
|
grinder = Grinder.new create_mocks(:blade, :chute, :bucket)
|
264
231
|
|
265
232
|
# Style 1: assertions on method args is done explicitly in block
|
@@ -290,7 +257,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
290
257
|
verify_mocks
|
291
258
|
end
|
292
259
|
|
293
|
-
|
260
|
+
it "further supports iteration testing using 'yield'" do
|
294
261
|
grinder = Grinder.new create_mocks(:blade, :chute, :bucket)
|
295
262
|
|
296
263
|
@chute.expects.each_bean(:side_slot).yields :bean1, :bean2
|
@@ -322,7 +289,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
322
289
|
end
|
323
290
|
end
|
324
291
|
|
325
|
-
|
292
|
+
it "makes mutex-style locking scenarios easy to test" do
|
326
293
|
hurt = HurtLocker.new create_mocks(:locker, :store)
|
327
294
|
|
328
295
|
@locker.expects.with_lock(:main).yields
|
@@ -333,7 +300,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
333
300
|
verify_mocks
|
334
301
|
end
|
335
302
|
|
336
|
-
|
303
|
+
it "makes it easy to simulate error in mutex-style locking scenarios" do
|
337
304
|
hurt = HurtLocker.new create_mocks(:locker, :store)
|
338
305
|
err = StandardError.new('fmshooop')
|
339
306
|
@locker.expects.with_lock(:main).yields
|
@@ -345,7 +312,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
345
312
|
verify_mocks
|
346
313
|
end
|
347
314
|
|
348
|
-
|
315
|
+
it "actually returns 'false' instead of nil when mocking boolean return values" do
|
349
316
|
create_mock :car
|
350
317
|
@car.expects.ignition_on?.returns(true)
|
351
318
|
assert_equal true, @car.ignition_on?, "Should be true"
|
@@ -353,7 +320,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
353
320
|
assert_equal false, @car.ignition_on?, "Should be false"
|
354
321
|
end
|
355
322
|
|
356
|
-
|
323
|
+
it "can mock most methods inherited from object using literal syntax" do
|
357
324
|
target_methods = %w|id clone display dup eql? ==|
|
358
325
|
create_mock :foo
|
359
326
|
target_methods.each do |m|
|
@@ -362,14 +329,14 @@ class HardmockTest < Test::Unit::TestCase
|
|
362
329
|
end
|
363
330
|
end
|
364
331
|
|
365
|
-
|
332
|
+
it "provides 'expect' as an alias for 'expects'" do
|
366
333
|
create_mock :foo
|
367
334
|
@foo.expect.boomboom
|
368
335
|
@foo.boomboom
|
369
336
|
verify_mocks
|
370
337
|
end
|
371
338
|
|
372
|
-
|
339
|
+
it "does not interfere with a core subset of Object methods" do
|
373
340
|
create_mock :foo
|
374
341
|
@foo.method(:inspect)
|
375
342
|
@foo.inspect
|
@@ -379,7 +346,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
379
346
|
verify_mocks
|
380
347
|
end
|
381
348
|
|
382
|
-
|
349
|
+
it "can raise errors from within an expectation block" do
|
383
350
|
create_mock :cat
|
384
351
|
@cat.expects.meow do |arg|
|
385
352
|
assert_equal "mix", arg
|
@@ -390,7 +357,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
390
357
|
end
|
391
358
|
end
|
392
359
|
|
393
|
-
|
360
|
+
it "can raise errors AFTER an expectation block" do
|
394
361
|
create_mock :cat
|
395
362
|
@cat.expects.meow do |arg|
|
396
363
|
assert_equal "mix", arg
|
@@ -400,7 +367,7 @@ class HardmockTest < Test::Unit::TestCase
|
|
400
367
|
end
|
401
368
|
end
|
402
369
|
|
403
|
-
|
370
|
+
it "raises an immediate error if a mock is created with a nil name (common mistake: create_mock @cat)" do
|
404
371
|
# I make this mistake all the time: Typing in an instance var name instead of a symbol in create_mocks.
|
405
372
|
# When you do that, you're effectively passing nil(s) in as mock names.
|
406
373
|
assert_error ArgumentError, /'nil' is not a valid name for a mock/ do
|
@@ -408,10 +375,33 @@ class HardmockTest < Test::Unit::TestCase
|
|
408
375
|
end
|
409
376
|
end
|
410
377
|
|
411
|
-
|
378
|
+
it "overrides 'inspect' to make nice output" do
|
412
379
|
create_mock :hay_bailer
|
413
380
|
assert_equal "<Mock hay_bailer>", @hay_bailer.inspect, "Wrong output from 'inspect'"
|
414
381
|
end
|
415
382
|
|
383
|
+
it "raises is prepare_hardmock_control is invoked after create_mocks, or more than once" do
|
384
|
+
create_mock :hi_there
|
385
|
+
create_mocks :another, :one
|
386
|
+
assert_error RuntimeError, /already setup/ do
|
387
|
+
prepare_hardmock_control
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
#
|
392
|
+
# HELPERS
|
393
|
+
#
|
394
|
+
|
395
|
+
def assert_mock_exists(name)
|
396
|
+
assert_not_nil @all_mocks, "@all_mocks not here yet"
|
397
|
+
mo = @all_mocks[name]
|
398
|
+
assert_not_nil mo, "Mock '#{name}' not in @all_mocks"
|
399
|
+
assert_kind_of Mock, mo, "Wrong type of object, wanted a Mock"
|
400
|
+
assert_equal name.to_s, mo._name, "Mock '#{name}' had wrong name"
|
401
|
+
ivar = self.instance_variable_get("@#{name}")
|
402
|
+
assert_not_nil ivar, "Mock '#{name}' not set as ivar"
|
403
|
+
assert_same mo, ivar, "Mock '#{name}' ivar not same as instance in @all_mocks"
|
404
|
+
assert_same @main_mock_control, mo._control, "Mock '#{name}' doesn't share the main mock control"
|
405
|
+
end
|
416
406
|
end
|
417
407
|
|
@@ -0,0 +1,274 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
|
+
require 'hardmock'
|
3
|
+
require 'assert_error'
|
4
|
+
|
5
|
+
class StubbingTest < Test::Unit::TestCase
|
6
|
+
|
7
|
+
#
|
8
|
+
# TESTS
|
9
|
+
#
|
10
|
+
|
11
|
+
it "stubs a class method (and un-stubs after verify)" do
|
12
|
+
assert_equal "stones and gravel", Concrete.pour
|
13
|
+
assert_equal "glug glug", Jug.pour
|
14
|
+
|
15
|
+
Concrete.stubs!(:pour).returns("dust and plaster")
|
16
|
+
|
17
|
+
3.times do
|
18
|
+
assert_equal "dust and plaster", Concrete.pour
|
19
|
+
end
|
20
|
+
|
21
|
+
assert_equal "glug glug", Jug.pour, "Jug's 'pour' method broken"
|
22
|
+
assert_equal "stones and gravel", Concrete._hardmock_original_pour, "Original 'pour' method not aliased"
|
23
|
+
|
24
|
+
assert_equal "For roads", Concrete.describe, "'describe' method broken"
|
25
|
+
|
26
|
+
verify_mocks
|
27
|
+
|
28
|
+
assert_equal "stones and gravel", Concrete.pour, "'pour' method not restored"
|
29
|
+
assert_equal "For roads", Concrete.describe, "'describe' method broken after verify"
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
it "stubs several class methods" do
|
34
|
+
Concrete.stubs!(:pour).returns("sludge")
|
35
|
+
Concrete.stubs!(:describe).returns("awful")
|
36
|
+
Jug.stubs!(:pour).returns("milk")
|
37
|
+
|
38
|
+
assert_equal "sludge", Concrete.pour
|
39
|
+
assert_equal "awful", Concrete.describe
|
40
|
+
assert_equal "milk", Jug.pour
|
41
|
+
|
42
|
+
verify_mocks
|
43
|
+
|
44
|
+
assert_equal "stones and gravel", Concrete.pour
|
45
|
+
assert_equal "For roads", Concrete.describe
|
46
|
+
assert_equal "glug glug", Jug.pour
|
47
|
+
end
|
48
|
+
|
49
|
+
it "stubs instance methods" do
|
50
|
+
slab = Concrete.new
|
51
|
+
assert_equal "bonk", slab.hit
|
52
|
+
|
53
|
+
slab.stubs!(:hit).returns("slap")
|
54
|
+
assert_equal "slap", slab.hit, "'hit' not stubbed"
|
55
|
+
|
56
|
+
verify_mocks
|
57
|
+
|
58
|
+
assert_equal "bonk", slab.hit, "'hit' not restored"
|
59
|
+
end
|
60
|
+
|
61
|
+
it "stubs instance methods without breaking class methods or other instances" do
|
62
|
+
slab = Concrete.new
|
63
|
+
scrape = Concrete.new
|
64
|
+
assert_equal "an instance", slab.describe
|
65
|
+
assert_equal "an instance", scrape.describe
|
66
|
+
assert_equal "For roads", Concrete.describe
|
67
|
+
|
68
|
+
slab.stubs!(:describe).returns("new instance describe")
|
69
|
+
assert_equal "new instance describe", slab.describe, "'describe' on instance not stubbed"
|
70
|
+
assert_equal "an instance", scrape.describe, "'describe' on 'scrape' instance broken"
|
71
|
+
assert_equal "For roads", Concrete.describe, "'describe' class method broken"
|
72
|
+
|
73
|
+
verify_mocks
|
74
|
+
|
75
|
+
assert_equal "an instance", slab.describe, "'describe' instance method not restored"
|
76
|
+
assert_equal "an instance", scrape.describe, "'describe' on 'scrape' instance broken after restore"
|
77
|
+
assert_equal "For roads", Concrete.describe, "'describe' class method broken after restore"
|
78
|
+
end
|
79
|
+
|
80
|
+
should "not allow stubbing of nonexistant class methods" do
|
81
|
+
assert_error(Hardmock::StubbingError, /cannot stub/i, /class method/i, /Concrete.funky/) do
|
82
|
+
Concrete.stubs!(:funky)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
should "not allow stubbing of nonexistant instance methods" do
|
87
|
+
assert_error(Hardmock::StubbingError, /cannot stub/i, /method/i, /Concrete#my_inst_mth/) do
|
88
|
+
Concrete.new.stubs!(:my_inst_mth)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
it "does nothing with a runtime block when simply stubbing" do
|
93
|
+
prepare_hardmock_control
|
94
|
+
slab = Concrete.new
|
95
|
+
slab.stubs!(:hit) do |nothing|
|
96
|
+
raise "BOOOMM!"
|
97
|
+
end
|
98
|
+
slab.hit
|
99
|
+
verify_mocks
|
100
|
+
end
|
101
|
+
|
102
|
+
|
103
|
+
#
|
104
|
+
# Per-method mocking on classes or instances
|
105
|
+
#
|
106
|
+
|
107
|
+
it "mocks specific methods on existing classes, and returns the class method to normal after verification" do
|
108
|
+
prepare_hardmock_control
|
109
|
+
assert_equal "stones and gravel", Concrete.pour, "Concrete.pour is already messed up"
|
110
|
+
|
111
|
+
Concrete.expects!(:pour).returns("ALIGATORS")
|
112
|
+
assert_equal "ALIGATORS", Concrete.pour
|
113
|
+
|
114
|
+
verify_mocks
|
115
|
+
assert_equal "stones and gravel", Concrete.pour, "Concrete.pour not restored"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "flunks if expected class method is not invoked" do
|
119
|
+
prepare_hardmock_control
|
120
|
+
Concrete.expects!(:pour).returns("ALIGATORS")
|
121
|
+
assert_error(Hardmock::VerifyError, /Concrete.pour/, /unmet expectations/i) do
|
122
|
+
verify_mocks
|
123
|
+
end
|
124
|
+
clear_expectations
|
125
|
+
end
|
126
|
+
|
127
|
+
it "supports all normal mock functionality for class methods" do
|
128
|
+
prepare_hardmock_control
|
129
|
+
Concrete.expects!(:pour, "two tons").returns("mice")
|
130
|
+
Concrete.expects!(:pour, "three tons").returns("cats")
|
131
|
+
Concrete.expects!(:pour, "four tons").raises("Can't do it")
|
132
|
+
Concrete.expects!(:pour) do |some, args|
|
133
|
+
"==#{some}+#{args}=="
|
134
|
+
end
|
135
|
+
|
136
|
+
assert_equal "mice", Concrete.pour("two tons")
|
137
|
+
assert_equal "cats", Concrete.pour("three tons")
|
138
|
+
assert_error(RuntimeError, /Can't do it/) do
|
139
|
+
Concrete.pour("four tons")
|
140
|
+
end
|
141
|
+
assert_equal "==first+second==", Concrete.pour("first","second")
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
it "enforces inter-mock ordering when mocking class methods" do
|
146
|
+
create_mocks :truck, :foreman
|
147
|
+
|
148
|
+
@truck.expects.backup
|
149
|
+
Concrete.expects!(:pour, "something")
|
150
|
+
@foreman.expects.shout
|
151
|
+
|
152
|
+
@truck.backup
|
153
|
+
assert_error Hardmock::ExpectationError, /wrong/i, /expected call/i, /Concrete.pour/ do
|
154
|
+
@foreman.shout
|
155
|
+
end
|
156
|
+
assert_error Hardmock::VerifyError, /unmet expectations/i, /foreman.shout/ do
|
157
|
+
verify_mocks
|
158
|
+
end
|
159
|
+
clear_expectations
|
160
|
+
end
|
161
|
+
|
162
|
+
should "not allow mocking non-existant class methods" do
|
163
|
+
prepare_hardmock_control
|
164
|
+
assert_error Hardmock::StubbingError, /non-existant/, /something/ do
|
165
|
+
Concrete.expects!(:something)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
it "mocks specific methods on existing instances, then restore them after verify" do
|
170
|
+
prepare_hardmock_control
|
171
|
+
slab = Concrete.new
|
172
|
+
assert_equal "bonk", slab.hit
|
173
|
+
|
174
|
+
slab.expects!(:hit).returns("slap")
|
175
|
+
assert_equal "slap", slab.hit, "'hit' not stubbed"
|
176
|
+
|
177
|
+
verify_mocks
|
178
|
+
assert_equal "bonk", slab.hit, "'hit' not restored"
|
179
|
+
end
|
180
|
+
|
181
|
+
it "flunks if expected instance method is not invoked" do
|
182
|
+
prepare_hardmock_control
|
183
|
+
slab = Concrete.new
|
184
|
+
slab.expects!(:hit)
|
185
|
+
|
186
|
+
assert_error Hardmock::VerifyError, /unmet expectations/i, /Concrete.hit/ do
|
187
|
+
verify_mocks
|
188
|
+
end
|
189
|
+
clear_expectations
|
190
|
+
end
|
191
|
+
|
192
|
+
it "supports all normal mock functionality for instance methods" do
|
193
|
+
prepare_hardmock_control
|
194
|
+
slab = Concrete.new
|
195
|
+
|
196
|
+
slab.expects!(:hit, "soft").returns("hey")
|
197
|
+
slab.expects!(:hit, "hard").returns("OOF")
|
198
|
+
slab.expects!(:hit).raises("stoppit")
|
199
|
+
slab.expects!(:hit) do |some, args|
|
200
|
+
"==#{some}+#{args}=="
|
201
|
+
end
|
202
|
+
|
203
|
+
assert_equal "hey", slab.hit("soft")
|
204
|
+
assert_equal "OOF", slab.hit("hard")
|
205
|
+
assert_error(RuntimeError, /stoppit/) do
|
206
|
+
slab.hit
|
207
|
+
end
|
208
|
+
assert_equal "==first+second==", slab.hit("first","second")
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
it "enforces inter-mock ordering when mocking instance methods" do
|
213
|
+
create_mocks :truck, :foreman
|
214
|
+
slab1 = Concrete.new
|
215
|
+
slab2 = Concrete.new
|
216
|
+
|
217
|
+
@truck.expects.backup
|
218
|
+
slab1.expects!(:hit)
|
219
|
+
@foreman.expects.shout
|
220
|
+
slab2.expects!(:hit)
|
221
|
+
@foreman.expects.whatever
|
222
|
+
|
223
|
+
@truck.backup
|
224
|
+
slab1.hit
|
225
|
+
@foreman.shout
|
226
|
+
assert_error Hardmock::ExpectationError, /wrong/i, /expected call/i, /Concrete.hit/ do
|
227
|
+
@foreman.whatever
|
228
|
+
end
|
229
|
+
assert_error Hardmock::VerifyError, /unmet expectations/i, /foreman.whatever/ do
|
230
|
+
verify_mocks
|
231
|
+
end
|
232
|
+
clear_expectations
|
233
|
+
end
|
234
|
+
|
235
|
+
should "not allow mocking non-existant instance methods" do
|
236
|
+
prepare_hardmock_control
|
237
|
+
slab = Concrete.new
|
238
|
+
assert_error Hardmock::StubbingError, /non-existant/, /something/ do
|
239
|
+
slab.expects!(:something)
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
should "support expectations that deal with runtime blocks"
|
244
|
+
|
245
|
+
#
|
246
|
+
# HELPERS
|
247
|
+
#
|
248
|
+
|
249
|
+
class Concrete
|
250
|
+
def self.pour
|
251
|
+
"stones and gravel"
|
252
|
+
end
|
253
|
+
|
254
|
+
def self.describe
|
255
|
+
"For roads"
|
256
|
+
end
|
257
|
+
|
258
|
+
def hit
|
259
|
+
"bonk"
|
260
|
+
end
|
261
|
+
|
262
|
+
def describe
|
263
|
+
"an instance"
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
class Jug
|
268
|
+
def self.pour
|
269
|
+
"glug glug"
|
270
|
+
end
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
data/test/test_helper.rb
CHANGED
@@ -20,4 +20,27 @@ class Test::Unit::TestCase
|
|
20
20
|
end
|
21
21
|
return false
|
22
22
|
end
|
23
|
+
|
24
|
+
def self.it(str, &block)
|
25
|
+
make_test_case "it", str, &block
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.should(str, &block)
|
29
|
+
make_test_case "should", str, &block
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.make_test_case(prefix, str, &block)
|
33
|
+
tname = self.name.sub(/Test$/,'')
|
34
|
+
if block
|
35
|
+
define_method "test #{prefix} #{str}" do
|
36
|
+
# phrase = str
|
37
|
+
# phrase = prefix + " " + phrase unless prefix == "it"
|
38
|
+
# puts "#{tname} #{phrase}..."
|
39
|
+
instance_eval &block
|
40
|
+
end
|
41
|
+
else
|
42
|
+
puts ">>> UNIMPLEMENTED CASE: #{tname}: #{str}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
23
46
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + "/../test_helper")
|
2
2
|
require 'hardmock/expectation'
|
3
3
|
require 'hardmock/errors'
|
4
|
+
require 'assert_error'
|
4
5
|
|
5
6
|
class ExpectationTest < Test::Unit::TestCase
|
6
7
|
include Hardmock
|
@@ -344,4 +345,14 @@ class ExpectationTest < Test::Unit::TestCase
|
|
344
345
|
assert_match(/"a", "b", "c"/i, err.message)
|
345
346
|
assert_equal [], things, "Wrong things"
|
346
347
|
end
|
348
|
+
|
349
|
+
def test_yields_bad_block_arity
|
350
|
+
se = Expectation.new(:mock => @mock, :method => 'do_later', :arguments => [] )
|
351
|
+
se.yields
|
352
|
+
|
353
|
+
assert_error Hardmock::ExpectationError, /block/i, /expected/i, /no param/i, /got 2/i do
|
354
|
+
se.apply_method_call(@mock,'do_later',[],lambda { |doesnt,match| raise "Surprise!" } )
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
347
358
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: hardmock
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-
|
6
|
+
version: 1.3.0
|
7
|
+
date: 2007-11-12 00:00:00 -05:00
|
8
8
|
summary: A strict, ordered, expectation-oriented mock object library.
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -38,6 +38,7 @@ files:
|
|
38
38
|
- lib/hardmock/method_cleanout.rb
|
39
39
|
- lib/hardmock/mock.rb
|
40
40
|
- lib/hardmock/mock_control.rb
|
41
|
+
- lib/hardmock/stubbing.rb
|
41
42
|
- lib/hardmock/trapper.rb
|
42
43
|
- lib/hardmock/utils.rb
|
43
44
|
- lib/tasks/rdoc_options.rb
|
@@ -46,6 +47,7 @@ files:
|
|
46
47
|
- test/functional/auto_verify_test.rb
|
47
48
|
- test/functional/direct_mock_usage_test.rb
|
48
49
|
- test/functional/hardmock_test.rb
|
50
|
+
- test/functional/stubbing_test.rb
|
49
51
|
- test/unit/expectation_builder_test.rb
|
50
52
|
- test/unit/expectation_test.rb
|
51
53
|
- test/unit/expector_test.rb
|
@@ -66,6 +68,7 @@ test_files:
|
|
66
68
|
- test/functional/auto_verify_test.rb
|
67
69
|
- test/functional/direct_mock_usage_test.rb
|
68
70
|
- test/functional/hardmock_test.rb
|
71
|
+
- test/functional/stubbing_test.rb
|
69
72
|
- test/unit/expectation_builder_test.rb
|
70
73
|
- test/unit/expectation_test.rb
|
71
74
|
- test/unit/expector_test.rb
|