flexmock 0.3.0 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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.0
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.0'
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
- return if @flexmock_created_mocks.nil?
695
- @flexmock_created_mocks.each do |m|
696
- m.mock_verify
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 /not_defined/, ex.message
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 /undefined local variable or method/, ex.message
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 /'mmm'/, ex.message
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
- assert_match /'mmm'/, ex.message
27
+ assert_match(/'mmm'/, ex.message)
28
28
  end
29
29
 
30
30
  def test_naming_with_use
@@ -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
@@ -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 setup
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.15
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.0
7
- date: 2006-05-14 00:00:00 -04:00
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/test_record_mode.rb
37
- - test/test_should_receive.rb
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/test_example.rb
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