flexmock 0.6.4 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
  ##########################################################################