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