flexmock 0.3.0 → 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +8 -0
- data/README +20 -1
- data/Rakefile +2 -1
- data/lib/flexmock.rb +165 -3
- data/test/test_class_interception.rb +108 -0
- data/test/test_mock.rb +2 -2
- data/test/test_naming.rb +2 -2
- data/test/test_should_receive.rb +20 -0
- data/test/test_tu_integration.rb +13 -8
- metadata +8 -8
data/CHANGELOG
CHANGED
@@ -1,5 +1,13 @@
|
|
1
1
|
= Changes for FlexMock
|
2
2
|
|
3
|
+
== Version 0.3.1
|
4
|
+
|
5
|
+
* Fixed some warnings regarding uniniitalized variables.
|
6
|
+
* Added (very) simple class interception.
|
7
|
+
* Added the mock_factory method to go along with class interception.
|
8
|
+
* When using Test::Unit integration, avoid mock verification if the
|
9
|
+
test has already failed for some reason.
|
10
|
+
|
3
11
|
== Version 0.3.0
|
4
12
|
|
5
13
|
* Added Test::Unit integration.
|
data/README
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
FlexMock is a simple mock object for unit testing. The interface is
|
4
4
|
simple, but still provides a good bit of flexibility.
|
5
5
|
|
6
|
-
Version :: 0.3.
|
6
|
+
Version :: 0.3.1
|
7
7
|
|
8
8
|
= Links
|
9
9
|
|
@@ -215,6 +215,25 @@ The following rules are used for argument matching:
|
|
215
215
|
|
216
216
|
will match any even integer.
|
217
217
|
|
218
|
+
=== Class Interception
|
219
|
+
|
220
|
+
FlexMock now support simple class interception. For the duration of a
|
221
|
+
test, a mock class take the place of a named class inside the class to
|
222
|
+
be tested.
|
223
|
+
|
224
|
+
Example:
|
225
|
+
|
226
|
+
Suppose we are testing class Foo, and Foo uses Bar internally. We
|
227
|
+
would like for Bar.new to return a mock object during the test.
|
228
|
+
|
229
|
+
def test_foo_with_an_intercepted_bar
|
230
|
+
my_mock = flexmock("my_mock").should_receive(....).mock
|
231
|
+
intercept(Bar).in(Foo).with(my_mock.mock_factory)
|
232
|
+
|
233
|
+
bar = Bar.new
|
234
|
+
bar.do_something
|
235
|
+
end
|
236
|
+
|
218
237
|
== Examples
|
219
238
|
|
220
239
|
=== Expect multiple queries and a single update
|
data/Rakefile
CHANGED
@@ -8,7 +8,7 @@ require 'rake/testtask'
|
|
8
8
|
|
9
9
|
CLOBBER.include("html", 'pkg')
|
10
10
|
|
11
|
-
PKG_VERSION = '0.3.
|
11
|
+
PKG_VERSION = '0.3.1'
|
12
12
|
|
13
13
|
PKG_FILES = FileList[
|
14
14
|
'[A-Z]*',
|
@@ -34,6 +34,7 @@ task :default => [:test]
|
|
34
34
|
Rake::TestTask.new do |t|
|
35
35
|
t.pattern = 'test/test*.rb'
|
36
36
|
t.verbose = true
|
37
|
+
t.warning = true
|
37
38
|
end
|
38
39
|
|
39
40
|
# RDoc Target --------------------------------------------------------
|
data/lib/flexmock.rb
CHANGED
@@ -60,6 +60,7 @@ class FlexMock
|
|
60
60
|
@allocated_order = 0
|
61
61
|
@mock_current_order = 0
|
62
62
|
@mock_groups = {}
|
63
|
+
@ignore_missing = false
|
63
64
|
end
|
64
65
|
|
65
66
|
# Handle all messages denoted by +sym+ by calling the given block
|
@@ -150,6 +151,12 @@ class FlexMock
|
|
150
151
|
yield Recorder.new(self)
|
151
152
|
end
|
152
153
|
|
154
|
+
# Return a factory object that returns this mock. This is useful in
|
155
|
+
# Class Interception.
|
156
|
+
def mock_factory
|
157
|
+
Factory.new(self)
|
158
|
+
end
|
159
|
+
|
153
160
|
class << self
|
154
161
|
include Test::Unit::Assertions
|
155
162
|
|
@@ -216,6 +223,27 @@ class FlexMock
|
|
216
223
|
ex.backtrace
|
217
224
|
end
|
218
225
|
|
226
|
+
####################################################################
|
227
|
+
# A Factory object is returned from a mock_factory method call. The
|
228
|
+
# factory merely returns the manufactured object it is initialized
|
229
|
+
# with. The factory is handy to use with class interception,
|
230
|
+
# allowing the intercepted class to return the mock object.
|
231
|
+
#
|
232
|
+
# If the user needs more control over the mock factory, they are
|
233
|
+
# free to create their own.
|
234
|
+
#
|
235
|
+
# Typical Usage:
|
236
|
+
# intercept(Bar).in(Foo).with(a_mock.mack_factory)
|
237
|
+
#
|
238
|
+
class Factory
|
239
|
+
def initialize(manufactured_object)
|
240
|
+
@obj = manufactured_object
|
241
|
+
end
|
242
|
+
def new(*args, &block)
|
243
|
+
@obj
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
219
247
|
####################################################################
|
220
248
|
# The expectation director is responsible for routing calls to the
|
221
249
|
# correct expectations for a given argument list.
|
@@ -691,9 +719,15 @@ class FlexMock
|
|
691
719
|
|
692
720
|
# Do the flexmock specific teardown stuff.
|
693
721
|
def flexmock_teardown
|
694
|
-
|
695
|
-
|
696
|
-
m
|
722
|
+
@flexmock_created_mocks ||= []
|
723
|
+
if passed?
|
724
|
+
@flexmock_created_mocks.each do |m|
|
725
|
+
m.mock_verify
|
726
|
+
end
|
727
|
+
end
|
728
|
+
@flexmock_interceptors ||= []
|
729
|
+
@flexmock_interceptors.each do |i|
|
730
|
+
i.restore
|
697
731
|
end
|
698
732
|
end
|
699
733
|
|
@@ -706,5 +740,133 @@ class FlexMock
|
|
706
740
|
@flexmock_created_mocks << result
|
707
741
|
result
|
708
742
|
end
|
743
|
+
|
744
|
+
# Intercept the named class in the target class for the duration
|
745
|
+
# of the test. Class interception is very simple-minded and has a
|
746
|
+
# number of restrictions. First, the intercepted class must be
|
747
|
+
# reference in the tested class via a simple constant name
|
748
|
+
# (e.g. no scoped names using "::") that is not directly defined
|
749
|
+
# in the class itself. After the test, a proxy class constant
|
750
|
+
# will be left behind that will forward all calls to the original
|
751
|
+
# class.
|
752
|
+
#
|
753
|
+
# Usage:
|
754
|
+
# intercept(SomeClass).in(ClassBeingTested).with(MockClass)
|
755
|
+
# intercept(SomeClass).with(MockClass).in(ClassBeingTested)
|
756
|
+
#
|
757
|
+
def intercept(intercepted_class)
|
758
|
+
result = Interception.new(intercepted_class)
|
759
|
+
@flexmock_interceptors ||= []
|
760
|
+
@flexmock_interceptors << result
|
761
|
+
result
|
762
|
+
end
|
709
763
|
end
|
764
|
+
|
765
|
+
####################################################################
|
766
|
+
# A Class Interception defines a constant in the target class to be
|
767
|
+
# a proxy that points to a replacement class for the duration of a
|
768
|
+
# test. When an interception is restored, the proxy will point to
|
769
|
+
# the original intercepted class.
|
770
|
+
#
|
771
|
+
class Interception
|
772
|
+
# Create an interception object with the class to intercepted.
|
773
|
+
def initialize(intercepted_class)
|
774
|
+
@intercepted = nil
|
775
|
+
@target = nil
|
776
|
+
@replacement = nil
|
777
|
+
intercept(intercepted_class)
|
778
|
+
update
|
779
|
+
end
|
780
|
+
|
781
|
+
# Intercept this class in the class to be tested.
|
782
|
+
def intercept(intercepted_class)
|
783
|
+
@intercepted = intercepted_class
|
784
|
+
update
|
785
|
+
self
|
786
|
+
end
|
787
|
+
|
788
|
+
# Define the class number test that will receive the
|
789
|
+
# interceptioned definition.
|
790
|
+
def in(target_class)
|
791
|
+
@target = target_class
|
792
|
+
update
|
793
|
+
self
|
794
|
+
end
|
795
|
+
|
796
|
+
# Define the replacement class. This is normally a proxy or a
|
797
|
+
# stub.
|
798
|
+
def with(replacement_class)
|
799
|
+
@replacement = replacement_class
|
800
|
+
update
|
801
|
+
self
|
802
|
+
end
|
803
|
+
|
804
|
+
# Restore the original class. The proxy remains in place however.
|
805
|
+
def restore
|
806
|
+
@proxy.proxied_class = @restore_class if @proxy
|
807
|
+
end
|
808
|
+
|
809
|
+
private
|
810
|
+
|
811
|
+
# Update the interception if the definition is complete.
|
812
|
+
def update
|
813
|
+
if complete?
|
814
|
+
do_interception
|
815
|
+
end
|
816
|
+
end
|
817
|
+
|
818
|
+
# Is the interception definition complete. In other words, are
|
819
|
+
# all three actors defined?
|
820
|
+
def complete?
|
821
|
+
@intercepted && @target && @replacement
|
822
|
+
end
|
823
|
+
|
824
|
+
# Implement interception on the classes defined.
|
825
|
+
def do_interception
|
826
|
+
@target_class = coerce_class(@target)
|
827
|
+
@replacement_class = coerce_class(@replacement)
|
828
|
+
case @intercepted
|
829
|
+
when String, Symbol
|
830
|
+
@intercepted_name = @intercepted.to_s
|
831
|
+
when Class
|
832
|
+
@intercepted_name = @intercepted.name
|
833
|
+
end
|
834
|
+
@intercepted_class = coerce_class(@intercepted)
|
835
|
+
current_class = @target_class.const_get(@intercepted_name)
|
836
|
+
if ClassProxy === current_class
|
837
|
+
@proxy = current_class
|
838
|
+
@restore_class = @proxy.proxied_class
|
839
|
+
@proxy.proxied_class = @replacement_class
|
840
|
+
else
|
841
|
+
@proxy = ClassProxy.new(@replacement_class)
|
842
|
+
@restore_class = current_class
|
843
|
+
@target_class.const_set(@intercepted_name, @proxy)
|
844
|
+
end
|
845
|
+
end
|
846
|
+
|
847
|
+
# Coerce a class object, string to symbol to be the class object.
|
848
|
+
def coerce_class(klass)
|
849
|
+
case klass
|
850
|
+
when String, Symbol
|
851
|
+
Object.const_get(klass.to_s)
|
852
|
+
when Class
|
853
|
+
klass
|
854
|
+
end
|
855
|
+
end
|
856
|
+
end
|
857
|
+
|
858
|
+
####################################################################
|
859
|
+
# Class Proxy for class interception. Forward all method calls to
|
860
|
+
# whatever is the proxied_class.
|
861
|
+
#
|
862
|
+
class ClassProxy
|
863
|
+
attr_accessor :proxied_class
|
864
|
+
def initialize(default_class)
|
865
|
+
@proxied_class = default_class
|
866
|
+
end
|
867
|
+
def method_missing(sym, *args, &block)
|
868
|
+
@proxied_class.__send__(sym, *args, &block)
|
869
|
+
end
|
870
|
+
end
|
871
|
+
|
710
872
|
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'flexmock'
|
5
|
+
|
6
|
+
module A
|
7
|
+
class B
|
8
|
+
def sample
|
9
|
+
:ab
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Bark
|
15
|
+
def listen
|
16
|
+
:woof
|
17
|
+
end
|
18
|
+
def Bark.identify
|
19
|
+
:barking
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Meow
|
24
|
+
def listen
|
25
|
+
:meow
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class Dog
|
30
|
+
def bark
|
31
|
+
Bark.new
|
32
|
+
end
|
33
|
+
def wag
|
34
|
+
A::B.new
|
35
|
+
end
|
36
|
+
def bark_id
|
37
|
+
Bark.identify
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class TestClassInterception < Test::Unit::TestCase
|
42
|
+
include FlexMock::TestCase
|
43
|
+
|
44
|
+
def test_original_functionality
|
45
|
+
dog = Dog.new
|
46
|
+
assert_equal :woof, dog.bark.listen
|
47
|
+
assert_equal :ab, dog.wag.sample
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_interception
|
51
|
+
interceptor = intercept(Bark).in(Dog).with(Meow)
|
52
|
+
assert_kind_of FlexMock::ClassProxy, Dog::Bark
|
53
|
+
assert_equal Meow, Dog::Bark.proxied_class
|
54
|
+
dog = Dog.new
|
55
|
+
assert_equal :meow, dog.bark.listen
|
56
|
+
ensure
|
57
|
+
interceptor.restore if interceptor
|
58
|
+
end
|
59
|
+
|
60
|
+
# Since interception leaves a proxy class behind, we want to make
|
61
|
+
# sure interception works twice on the same object. So we repeat
|
62
|
+
# this test in full. We don't know which tests will _actually_ run
|
63
|
+
# first, but it doesn't matter as long as it is done twice.
|
64
|
+
def test_interception_repeated
|
65
|
+
test_interception
|
66
|
+
end
|
67
|
+
|
68
|
+
def test_interception_with_strings
|
69
|
+
interceptor = intercept("Bark").in("Dog").with("Meow")
|
70
|
+
assert_equal :meow, Dog.new.bark.listen
|
71
|
+
ensure
|
72
|
+
interceptor.restore if interceptor
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_interception_with_symbols
|
76
|
+
interceptor = intercept(:Bark).in(:Dog).with(:Meow)
|
77
|
+
assert_equal :meow, Dog.new.bark.listen
|
78
|
+
ensure
|
79
|
+
interceptor.restore if interceptor
|
80
|
+
end
|
81
|
+
|
82
|
+
def test_interception_with_mixed_identifiers
|
83
|
+
interceptor = intercept(:Bark).in("Dog").with(Meow)
|
84
|
+
assert_equal :meow, Dog.new.bark.listen
|
85
|
+
ensure
|
86
|
+
interceptor.restore if interceptor
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_interception_proxy_forward_other_methods
|
90
|
+
d = Dog.new
|
91
|
+
assert_equal :barking, d.bark_id
|
92
|
+
|
93
|
+
interceptor = intercept(:Bark).in("Dog").with(Meow)
|
94
|
+
interceptor.restore
|
95
|
+
|
96
|
+
d = Dog.new
|
97
|
+
assert_equal :barking, d.bark_id
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
class TestMockFactory < Test::Unit::TestCase
|
103
|
+
def test_mock_returns_factory
|
104
|
+
m = FlexMock.new("m")
|
105
|
+
f = m.mock_factory
|
106
|
+
assert_equal m, f.new
|
107
|
+
end
|
108
|
+
end
|
data/test/test_mock.rb
CHANGED
@@ -38,7 +38,7 @@ class TestFlexMock < Test::Unit::TestCase
|
|
38
38
|
ex = assert_raises(expected_error) {
|
39
39
|
@mock.not_defined
|
40
40
|
}
|
41
|
-
assert_match
|
41
|
+
assert_match(/not_defined/, ex.message)
|
42
42
|
end
|
43
43
|
|
44
44
|
def test_ignore_missing_method
|
@@ -116,7 +116,7 @@ class TestFlexMock < Test::Unit::TestCase
|
|
116
116
|
xyz
|
117
117
|
end
|
118
118
|
}
|
119
|
-
assert_match
|
119
|
+
assert_match(/undefined local variable or method/, ex.message)
|
120
120
|
end
|
121
121
|
|
122
122
|
def test_sequential_values
|
data/test/test_naming.rb
CHANGED
@@ -15,7 +15,7 @@ class TestNaming < Test::Unit::TestCase
|
|
15
15
|
m.should_receive(:xx).with(1)
|
16
16
|
m.xx(2)
|
17
17
|
}
|
18
|
-
assert_match
|
18
|
+
assert_match(/'mmm'/, ex.message)
|
19
19
|
end
|
20
20
|
|
21
21
|
def test_name_in_received_count_error
|
@@ -24,7 +24,7 @@ class TestNaming < Test::Unit::TestCase
|
|
24
24
|
m.should_receive(:xx).once
|
25
25
|
m.mock_verify
|
26
26
|
}
|
27
|
-
|
27
|
+
assert_match(/'mmm'/, ex.message)
|
28
28
|
end
|
29
29
|
|
30
30
|
def test_naming_with_use
|
data/test/test_should_receive.rb
CHANGED
@@ -171,6 +171,26 @@ class TestFlexMockShoulds < Test::Unit::TestCase
|
|
171
171
|
end
|
172
172
|
end
|
173
173
|
|
174
|
+
def test_block_arg_given_to_no_args
|
175
|
+
FlexMock.use do |m|
|
176
|
+
m.should_receive(:hi).with_no_args.returns(20)
|
177
|
+
assert_failure {
|
178
|
+
m.hi { 1 }
|
179
|
+
}
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def test_block_arg_given_to_matching_proc
|
184
|
+
FlexMock.use do |m|
|
185
|
+
arg = nil
|
186
|
+
m.should_receive(:hi).with(Proc).once.
|
187
|
+
and_return { |block| arg = block; block.call }
|
188
|
+
result = m.hi { 1 }
|
189
|
+
assert_equal 1, arg.call
|
190
|
+
assert_equal 1, result
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
174
194
|
def test_arg_matching_precedence_when_best_first
|
175
195
|
FlexMock.use("greeter") do |m|
|
176
196
|
m.should_receive(:hi).with(1).once
|
data/test/test_tu_integration.rb
CHANGED
@@ -6,10 +6,6 @@ require 'flexmock'
|
|
6
6
|
class TestTuIntegrationMockVerificationInTeardown < Test::Unit::TestCase
|
7
7
|
include FlexMock::TestCase
|
8
8
|
|
9
|
-
def setup
|
10
|
-
super
|
11
|
-
end
|
12
|
-
|
13
9
|
def teardown
|
14
10
|
assert_raise(Test::Unit::AssertionFailedError) do
|
15
11
|
super
|
@@ -38,9 +34,6 @@ end
|
|
38
34
|
class TestTuIntegrationMockVerificationForgetfulSetup < Test::Unit::TestCase
|
39
35
|
include FlexMock::TestCase
|
40
36
|
|
41
|
-
def setup
|
42
|
-
end
|
43
|
-
|
44
37
|
def teardown
|
45
38
|
assert_raise(Test::Unit::AssertionFailedError) do
|
46
39
|
super
|
@@ -55,9 +48,21 @@ end
|
|
55
48
|
class TestTuIntegrationSetupOverridenAndNoMocksOk < Test::Unit::TestCase
|
56
49
|
include FlexMock::TestCase
|
57
50
|
|
58
|
-
def
|
51
|
+
def test_mock_verification_occurs_during_teardown
|
59
52
|
end
|
53
|
+
end
|
54
|
+
|
55
|
+
class TestTuIntegrationFailurePreventsVerification < Test::Unit::TestCase
|
56
|
+
include FlexMock::TestCase
|
60
57
|
|
61
58
|
def test_mock_verification_occurs_during_teardown
|
59
|
+
flexmock('m').should_receive(:hi).once
|
60
|
+
simulate_failure
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def simulate_failure
|
66
|
+
@test_passed = false
|
62
67
|
end
|
63
68
|
end
|
metadata
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.8.11
|
2
|
+
rubygems_version: 0.8.11
|
3
3
|
specification_version: 1
|
4
4
|
name: flexmock
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.3.
|
7
|
-
date: 2006-
|
6
|
+
version: 0.3.1
|
7
|
+
date: 2006-06-17 00:00:00 -04:00
|
8
8
|
summary: Simple and Flexible Mock Objects for Testing
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -25,7 +25,6 @@ required_ruby_version: !ruby/object:Gem::Version::Requirement
|
|
25
25
|
platform: ruby
|
26
26
|
signing_key:
|
27
27
|
cert_chain:
|
28
|
-
post_install_message:
|
29
28
|
authors:
|
30
29
|
- Jim Weirich
|
31
30
|
files:
|
@@ -33,12 +32,13 @@ files:
|
|
33
32
|
- Rakefile
|
34
33
|
- README
|
35
34
|
- lib/flexmock.rb
|
36
|
-
- test/
|
37
|
-
- test/
|
38
|
-
- test/test_samples.rb
|
35
|
+
- test/test_class_interception.rb
|
36
|
+
- test/test_example.rb
|
39
37
|
- test/test_mock.rb
|
40
38
|
- test/test_naming.rb
|
41
|
-
- test/
|
39
|
+
- test/test_record_mode.rb
|
40
|
+
- test/test_samples.rb
|
41
|
+
- test/test_should_receive.rb
|
42
42
|
- test/test_tu_integration.rb
|
43
43
|
- flexmock.blurb
|
44
44
|
- install.rb
|