flexmock 0.6.4 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,138 @@
1
+ = FlexMock 0.7.0 Released
2
+
3
+ FlexMock is a flexible mocking library for use in unit testing and behavior
4
+ specification in Ruby. Version 0.7.0 introduces several enhancements.
5
+
6
+ == New in 0.7.0
7
+
8
+ * FlexMock now supports the ability to mock a chain of method calls
9
+ automatically. For example:
10
+
11
+ car = flexmock("car", "chassis.engine.piston.stroke" => :ok)
12
+ assert_equal :ok, car.chassis.engine.piston.stroke
13
+
14
+ will create a sequence of mocks so that the "chassis" call will
15
+ return a mock that responds to "engine", which returns a mock that
16
+ responds to "piston", which returns a mock that responds to
17
+ "stroke". This facility makes mocking legacy code that violates the
18
+ Law of Demeter a bit easier to deal with.
19
+
20
+ * Added the the +and_yield+ constraint to FlexMock expectations. This
21
+ allows the user to easily specify values passed to any block given
22
+ to the mock method.
23
+
24
+ * Globally ordering of mocked calls is now optionally available. When
25
+ a mock method is globally ordered, it must be called in the correct
26
+ order with regard to all other globally ordered methods. Non-global
27
+ ordering only requires that the method calls be ordered with regard
28
+ to other methods on the same mock object.
29
+
30
+ * The output for mock.inspect was modified to be much more consise, so
31
+ that test framework error messages do not overwhelm the output.
32
+
33
+ * In order to clean up the method namespace, a number of internally
34
+ used methods were deprecated. All non-public methods that get added
35
+ to mocks, partial mocks or test frameworks now begin with
36
+ "flexmock_" (rather than "mock_"). The "mock_*" versions are still
37
+ available, but will display deprecation warnings when used. The
38
+ deprecated "mock_*" methods will be removed in a future version.
39
+
40
+ * Additionally, the ancient "mock_handle" method has been deprecated
41
+ (prints a warning when used), and will be removed in a future
42
+ version. Users are encouraged to use the newer "should_receive"
43
+ method instead.
44
+
45
+ == New Features Added in 0.6.x
46
+
47
+ In case you missed them, here are a number of features that were added
48
+ during the 0.6.x versions of FlexMock.
49
+
50
+ * ActiveRecord mocking support with flexmock(:model, ModelName).
51
+
52
+ * Better partial mock definitions, including a "safe-mode" that
53
+ minimizes mock namespace pollution in the domain object.
54
+
55
+ * Support for +and_raise+ constraint to ease the definition of mocks
56
+ that raise exceptions.
57
+
58
+ == What is FlexMock?
59
+
60
+ FlexMock is a flexible framework for creating mock object for testing. When
61
+ running unit tests, it is often desirable to use isolate the objects being
62
+ tested from the "real world" by having them interact with simplified test
63
+ objects. Sometimes these test objects simply return values when called, other
64
+ times they verify that certain methods were called with particular arguments
65
+ in a particular order.
66
+
67
+ FlexMock makes creating these test objects easy.
68
+
69
+ === Features
70
+
71
+ * Easy integration with both Test::Unit and RSpec. Mocks created with the
72
+ flexmock method are automatically verified at the end of the test or
73
+ example.
74
+
75
+ * A fluent interface that allows mock behavior to be specified very
76
+ easily.
77
+
78
+ * A "record mode" where an existing implementation can record its
79
+ interaction with a mock for later validation against a new
80
+ implementation.
81
+
82
+ * Easy mocking of individual methods in existing, non-mock objects.
83
+
84
+ * The ability to cause classes to instantiate test instances (instead of real
85
+ instances) for the duration of a test.
86
+
87
+ === Example
88
+
89
+ Suppose you had a Dog object that wagged a tail when it was happy.
90
+ Something like this:
91
+
92
+ class Dog
93
+ def initialize(a_tail)
94
+ @tail = a_tail
95
+ end
96
+ def happy
97
+ @tail.wag
98
+ end
99
+ end
100
+
101
+ To test the +Dog+ class without a real +Tail+ object (perhaps because
102
+ real +Tail+ objects activate servos in some robotic equipment), you
103
+ can do something like this:
104
+
105
+ require 'test/unit'
106
+ require 'flexmock/test_unit'
107
+
108
+ class TestDog < Test::Unit::TestCase
109
+ def test_dog_wags_tail_when_happy
110
+ tail = flexmock("tail")
111
+ tail.should_receive(:wag).once
112
+ dog = Dog.new(tail)
113
+ dog.happy
114
+ end
115
+ end
116
+
117
+ FlexMock will automatically verify that the mocked tail object received the
118
+ message +wag+ exactly one time. If it doesn't, the test will not pass.
119
+
120
+ See the FlexMock documentation at http://flexmock.rubyforge.org for details on
121
+ specifying arguments and return values on mocked methods, as well as a simple
122
+ technique for mocking tail objects when the Dog class creates the tail objects
123
+ directly.
124
+
125
+ == Availability
126
+
127
+ You can make sure you have the latest version with a quick RubyGems command:
128
+
129
+ gem install flexmock (you may need root/admin privileges)
130
+
131
+ Otherwise, you can get it from the more traditional places:
132
+
133
+ Download:: http://rubyforge.org/project/showfiles.php?group_id=170
134
+
135
+ You will find documentation at: http://flexmock.rubyforge.org.
136
+
137
+ -- Jim Weirich
138
+
@@ -19,4 +19,5 @@ require 'flexmock/argument_types'
19
19
  require 'flexmock/validators'
20
20
  require 'flexmock/recorder'
21
21
  require 'flexmock/mock_container'
22
- require 'flexmock/partial_mock'
22
+ require 'flexmock/partial_mock'
23
+ require 'flexmock/deprecated_methods'
@@ -10,6 +10,7 @@
10
10
  #+++
11
11
 
12
12
  require 'flexmock/composite'
13
+ require 'flexmock/ordering'
13
14
 
14
15
  ######################################################################
15
16
  # FlexMock is a flexible mock object framework for supporting testing.
@@ -42,55 +43,49 @@ require 'flexmock/composite'
42
43
  # call +super+.
43
44
  #
44
45
  class FlexMock
46
+ include Ordering
45
47
 
46
48
  # Error raised when flexmock is used incorrectly.
47
49
  class UsageError < RuntimeError
48
50
  end
49
51
 
50
- attr_reader :mock_name, :mock_groups
51
- attr_accessor :mock_current_order, :mock_container
52
+ class MockError < RuntimeError
53
+ end
54
+
55
+ attr_reader :flexmock_name
56
+ attr_accessor :flexmock_container
52
57
 
53
58
  # Create a FlexMock object with the given name. The name is used in
54
- # error messages.
55
- def initialize(name="unknown")
56
- @mock_name = name
59
+ # error messages. If no container is given, create a new, one-off
60
+ # container for this mock.
61
+ def initialize(name="unknown", container=nil)
62
+ @flexmock_name = name
57
63
  @expectations = Hash.new
58
- @allocated_order = 0
59
- @mock_current_order = 0
60
- @mock_container = nil
61
- @mock_groups = {}
62
64
  @ignore_missing = false
63
65
  @verified = false
66
+ container = UseContainer.new if container.nil?
67
+ container.flexmock_remember(self)
64
68
  end
65
69
 
66
- # Handle all messages denoted by +sym+ by calling the given block
67
- # and passing any parameters to the block. If we know exactly how
68
- # many calls are to be made to a particular method, we may check
69
- # that by passing in the number of expected calls as a second
70
- # paramter.
71
- def mock_handle(sym, expected_count=nil, &block) # :nodoc:
72
- self.should_receive(sym).times(expected_count).returns(&block)
70
+ # Return the inspection string for a mock.
71
+ def inspect
72
+ "<FlexMock:#{flexmock_name}>"
73
73
  end
74
74
 
75
75
  # Verify that each method that had an explicit expected count was
76
76
  # actually called that many times.
77
- def mock_verify
77
+ def flexmock_verify
78
78
  return if @verified
79
79
  @verified = true
80
- mock_wrap do
80
+ flexmock_wrap do
81
81
  @expectations.each do |sym, handler|
82
- handler.mock_verify
82
+ handler.flexmock_verify
83
83
  end
84
84
  end
85
85
  end
86
86
 
87
87
  # Teardown and infrastructure setup for this mock.
88
- def mock_teardown
89
- end
90
-
91
- # Allocation a new order number from the mock.
92
- def mock_allocate_order
93
- @allocated_order += 1
88
+ def flexmock_teardown
94
89
  end
95
90
 
96
91
  # Ignore all undefined (missing) method calls.
@@ -101,7 +96,7 @@ class FlexMock
101
96
 
102
97
  # Handle missing methods by attempting to look up a handler.
103
98
  def method_missing(sym, *args, &block)
104
- mock_wrap do
99
+ flexmock_wrap do
105
100
  if handler = @expectations[sym]
106
101
  args << block if block_given?
107
102
  handler.call(*args)
@@ -119,6 +114,12 @@ class FlexMock
119
114
  super || (@expectations[sym] ? true : @ignore_missing)
120
115
  end
121
116
 
117
+ # Find the mock expectation for method sym and arguments.
118
+ def flexmock_find_expectation(sym, *args)
119
+ exp = @expectations[sym]
120
+ exp ? exp.find_expectation(*args) : nil
121
+ end
122
+
122
123
  # Override the built-in +method+ to include the mocked methods.
123
124
  def method(sym)
124
125
  @expectations[sym] || super
@@ -150,7 +151,7 @@ class FlexMock
150
151
  # See Expectation for a list of declarators that can be used.
151
152
  #
152
153
  def should_receive(*args)
153
- FlexMock.should_receive(args) do |sym|
154
+ ContainerHelper.parse_should_args(self, args) do |sym|
154
155
  @expectations[sym] ||= ExpectationDirector.new(sym)
155
156
  result = Expectation.new(self, sym)
156
157
  @expectations[sym] << result
@@ -179,11 +180,11 @@ class FlexMock
179
180
 
180
181
  # Wrap a block of code so the any assertion errors are wrapped so
181
182
  # that the mock name is added to the error message .
182
- def mock_wrap(&block)
183
+ def flexmock_wrap(&block)
183
184
  yield
184
185
  rescue FlexMock.framework_adapter.assertion_failed_error => ex
185
186
  raise FlexMock.framework_adapter.assertion_failed_error,
186
- "in mock '#{@mock_name}': #{ex.message}",
187
+ "in mock '#{@flexmock_name}': #{ex.message}",
187
188
  ex.backtrace
188
189
  end
189
190
 
@@ -10,34 +10,12 @@
10
10
  #+++
11
11
 
12
12
  require 'flexmock/noop'
13
+ require 'flexmock/mock_container'
13
14
 
14
15
  class FlexMock
15
16
  class << self
16
17
  attr_reader :framework_adapter
17
18
 
18
- # :call-seq:
19
- # should_receive(args) { |symbol| ... }
20
- #
21
- # This method provides common handling for the various should_receive
22
- # argument lists. It sorts out the differences between symbols, arrays and
23
- # hashes, and identifies the method names specified by each. As each
24
- # method name is identified, create a mock expectation for it using the
25
- # supplied block.
26
- def should_receive(args) # :nodoc:
27
- result = CompositeExpectation.new
28
- args.each do |arg|
29
- case arg
30
- when Hash
31
- arg.each do |k,v|
32
- result.add(yield(k.to_sym).and_return(v))
33
- end
34
- when Symbol, String
35
- result.add(yield(arg.to_sym))
36
- end
37
- end
38
- result
39
- end
40
-
41
19
  # Class method to make sure that verify is called at the end of a
42
20
  # test. One mock object will be created for each name given to
43
21
  # the use method. The mocks will be passed to the block as
@@ -60,16 +38,14 @@ class FlexMock
60
38
  #
61
39
  def use(*names)
62
40
  names = ["unknown"] if names.empty?
63
- got_excecption = false
64
- mocks = names.collect { |n| new(n) }
41
+ container = UseContainer.new
42
+ mocks = names.collect { |n| container.flexmock(n) }
65
43
  yield(*mocks)
66
44
  rescue Exception => ex
67
- got_exception = true
45
+ container.got_exception = true
68
46
  raise
69
47
  ensure
70
- mocks.each do |mock|
71
- mock.mock_verify unless got_exception
72
- end
48
+ container.flexmock_teardown
73
49
  end
74
50
 
75
51
  # Class method to format a method name and argument list as a nice
@@ -87,6 +63,21 @@ class FlexMock
87
63
  def check(msg, &block) # :nodoc:
88
64
  FlexMock.framework_adapter.assert_block(msg, &block)
89
65
  end
66
+
90
67
  end
91
68
 
92
- end
69
+ # Container object to be used by the FlexMock.use method.
70
+ class UseContainer
71
+ include MockContainer
72
+
73
+ attr_accessor :got_exception
74
+
75
+ def initialize
76
+ @got_exception = false
77
+ end
78
+
79
+ def passed?
80
+ ! got_exception
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ #---
4
+ # Copyright 2003, 2004, 2005, 2006, 2007 by Jim Weirich (jim@weirichhouse.org).
5
+ # All rights reserved.
6
+ #
7
+ # Permission is granted for use, copying, modification, distribution,
8
+ # and distribution of modified versions of this work as long as the
9
+ # above copyright notice is included.
10
+ #+++
11
+
12
+
13
+ class Module
14
+ def flexmock_deprecate(*method_names)
15
+ method_names.each do |method_name|
16
+ eval_line = __LINE__ + 1
17
+ module_eval %{
18
+ def #{method_name}(*args)
19
+ $stderr.puts "#{method_name} is deprecated, use flex#{method_name} instead"
20
+ flex#{method_name}(*args)
21
+ end
22
+ }, __FILE__, eval_line
23
+ end
24
+ end
25
+ end
26
+
27
+ # Deprecated Methods
28
+ # ==================
29
+ #
30
+ # The following methods are no longer supported in FlexMock. Include
31
+ # this file for legacy applications.
32
+ #
33
+ class FlexMock
34
+
35
+ # Handle all messages denoted by +sym+ by calling the given block
36
+ # and passing any parameters to the block. If we know exactly how
37
+ # many calls are to be made to a particular method, we may check
38
+ # that by passing in the number of expected calls as a second
39
+ # paramter.
40
+ def mock_handle(sym, expected_count=nil, &block) # :nodoc:
41
+ $stderr.puts "mock_handle is deprecated, use the new should_receive interface instead."
42
+ self.should_receive(sym).times(expected_count).returns(&block)
43
+ end
44
+
45
+ flexmock_deprecate :mock_verify, :mock_teardown, :mock_wrap
46
+
47
+ class PartialMockProxy
48
+
49
+ MOCK_METHODS << :any_instance
50
+
51
+ # any_instance is present for backwards compatibility with version 0.5.0.
52
+ # @deprecated
53
+ def any_instance(&block)
54
+ $stderr.puts "any_instance is deprecated, use new_instances instead."
55
+ new_instances(&block)
56
+ end
57
+ end
58
+
59
+ module Ordering
60
+ flexmock_deprecate :mock_allocate_order, :mock_groups, :mock_current_order, :mock_validate_order
61
+ end
62
+ end
@@ -39,8 +39,11 @@ class FlexMock
39
39
  @count_validator_class = ExactCountValidator
40
40
  @actual_count = 0
41
41
  @return_value = nil
42
- @return_block = lambda { @return_value }
42
+ @return_queue = []
43
+ @yield_queue = []
43
44
  @order_number = nil
45
+ @global_order_number = nil
46
+ @globally = nil
44
47
  end
45
48
 
46
49
  def to_s
@@ -52,9 +55,45 @@ class FlexMock
52
55
  def verify_call(*args)
53
56
  validate_order
54
57
  @actual_count += 1
55
- @return_block.call(*args)
58
+ perform_yielding(args)
59
+ return_value(args)
56
60
  end
57
61
 
62
+ # Public return value (odd name to avoid accidental use as a
63
+ # constraint).
64
+ def _return_value(args) # :nodoc:
65
+ return_value(args)
66
+ end
67
+
68
+ # Find the return value for this expectation. (private version)
69
+ def return_value(args)
70
+ case @return_queue.size
71
+ when 0
72
+ block = lambda { @return_value }
73
+ when 1
74
+ block = @return_queue.first
75
+ else
76
+ block = @return_queue.shift
77
+ end
78
+ block.call(*args)
79
+ end
80
+ private :return_value
81
+
82
+ # Yield stored values to any blocks given.
83
+ def perform_yielding(args)
84
+ @return_value = nil
85
+ unless @yield_queue.empty?
86
+ block = args.last
87
+ values = (@yield_queue.size == 1) ? @yield_queue.first : @yield_queue.shift
88
+ if block.respond_to?(:call)
89
+ @return_value = block.call(*values)
90
+ else
91
+ fail MockError, "No Block given to mock with 'and_yield' expectation"
92
+ end
93
+ end
94
+ end
95
+ private :perform_yielding
96
+
58
97
  # Is this expectation eligible to be called again? It is eligible
59
98
  # only if all of its count validators agree that it is eligible.
60
99
  def eligible?
@@ -63,18 +102,18 @@ class FlexMock
63
102
 
64
103
  # Validate that the order
65
104
  def validate_order
66
- return if @order_number.nil?
67
- FlexMock.check("method #{to_s} called out of order " +
68
- "(expected order #{@order_number}, was #{@mock.mock_current_order})") {
69
- @order_number >= @mock.mock_current_order
70
- }
71
- @mock.mock_current_order = @order_number
105
+ if @order_number
106
+ @mock.flexmock_validate_order(to_s, @order_number)
107
+ end
108
+ if @global_order_number
109
+ @mock.flexmock_container.flexmock_validate_order(to_s, @global_order_number)
110
+ end
72
111
  end
73
112
  private :validate_order
74
113
 
75
114
  # Validate the correct number of calls have been made. Called by
76
115
  # the teardown process.
77
- def mock_verify
116
+ def flexmock_verify
78
117
  @count_validators.each do |v|
79
118
  v.validate(@actual_count)
80
119
  end
@@ -142,16 +181,22 @@ class FlexMock
142
181
  # +returns+ is an alias for +and_return+.
143
182
  #
144
183
  def and_return(*args, &block)
145
- @return_block =
146
- if block_given?
147
- block
148
- else
149
- lambda { args.size == 1 ? args.first : args.shift }
184
+ if block_given?
185
+ @return_queue << block
186
+ else
187
+ args.each do |arg|
188
+ @return_queue << lambda { arg }
150
189
  end
190
+ end
151
191
  self
152
192
  end
153
193
  alias :returns :and_return # :nodoc:
154
194
 
195
+ def and_yield(*yield_values)
196
+ @yield_queue << yield_values
197
+ end
198
+ alias :yields :and_yield
199
+
155
200
 
156
201
  # :call-seq:
157
202
  # and_raise(an_exception)
@@ -169,13 +214,27 @@ class FlexMock
169
214
  # additional arguments in the argument list will be passed to
170
215
  # the +new+ constructor when it is invoked.
171
216
  #
172
- # +raises+ is an alias for +and_return+.
217
+ # +raises+ is an alias for +and_raise+.
173
218
  #
174
219
  def and_raise(exception, *args)
175
220
  and_return { raise exception, *args }
176
221
  end
177
222
  alias :raises :and_raise
178
223
 
224
+ # :call-seq:
225
+ # and_throw(a_symbol)
226
+ # and_throw(a_symbol, value)
227
+ #
228
+ # Declares that the method will throw the given symbol (with an
229
+ # optional value) when executed.
230
+ #
231
+ # +throws+ is an alias for +and_throw+.
232
+ #
233
+ def and_throw(sym, value=nil)
234
+ and_return { throw sym, value }
235
+ end
236
+ alias :throws :and_throw
237
+
179
238
  # Declare that the method may be called any number of times.
180
239
  def zero_or_more_times
181
240
  at_least.never
@@ -262,16 +321,37 @@ class FlexMock
262
321
  # m.should_receive(:end).ordered
263
322
  #
264
323
  def ordered(group_name=nil)
265
- if group_name.nil?
266
- @order_number = @mock.mock_allocate_order
267
- elsif (num = @mock.mock_groups[group_name])
268
- @order_number = num
324
+ if @globally
325
+ @global_order_number = define_ordered(group_name, @mock.flexmock_container)
269
326
  else
270
- @order_number = @mock.mock_allocate_order
271
- @mock.mock_groups[group_name] = @order_number
327
+ @order_number = define_ordered(group_name, @mock)
272
328
  end
329
+ @globally = false
273
330
  self
274
331
  end
332
+
333
+ # Modifier that changes the next ordered constraint to apply
334
+ # globally across all mock objects in the container.
335
+ def globally
336
+ @globally = true
337
+ self
338
+ end
339
+
340
+ # Helper method for defining ordered expectations.
341
+ def define_ordered(group_name, ordering)
342
+ fail UsageError, "Mock #{@mock.flexmock_name} is not in a container and cannot be globally ordered." if ordering.nil?
343
+ if group_name.nil?
344
+ result = ordering.flexmock_allocate_order
345
+ elsif (num = ordering.flexmock_groups[group_name])
346
+ result = num
347
+ else
348
+ result = ordering.flexmock_allocate_order
349
+ ordering.flexmock_groups[group_name] = result
350
+ end
351
+ result
352
+ end
353
+ private :define_ordered
354
+
275
355
  end
276
356
 
277
357
  ##########################################################################