mocha 0.14.0 → 1.0.0.alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +28 -8
  3. data/RELEASE.md +40 -0
  4. data/bin/build-matrix +71 -0
  5. data/lib/mocha.rb +0 -7
  6. data/lib/mocha/any_instance_method.rb +2 -2
  7. data/lib/mocha/class_method.rb +5 -2
  8. data/lib/mocha/detection/mini_test.rb +25 -0
  9. data/lib/mocha/detection/test_unit.rb +29 -0
  10. data/lib/mocha/expectation_list.rb +6 -2
  11. data/lib/mocha/integration.rb +2 -2
  12. data/lib/mocha/integration/mini_test.rb +6 -11
  13. data/lib/mocha/integration/test_unit.rb +4 -9
  14. data/lib/mocha/mini_test.rb +3 -0
  15. data/lib/mocha/mock.rb +73 -8
  16. data/lib/mocha/mockery.rb +3 -2
  17. data/lib/mocha/object_methods.rb +4 -2
  18. data/lib/mocha/parameter_matchers/responds_with.rb +1 -1
  19. data/lib/mocha/receivers.rb +49 -0
  20. data/lib/mocha/setup.rb +0 -1
  21. data/lib/mocha/test_unit.rb +3 -0
  22. data/lib/mocha/unexpected_invocation.rb +10 -5
  23. data/lib/mocha/version.rb +1 -1
  24. data/test/acceptance/stub_any_instance_method_defined_on_superclass_test.rb +34 -0
  25. data/test/acceptance/stub_any_instance_method_test.rb +1 -0
  26. data/test/acceptance/stub_class_method_defined_on_class_test.rb +3 -0
  27. data/test/acceptance/stub_class_method_defined_on_superclass_test.rb +40 -3
  28. data/test/acceptance/stub_instance_method_defined_on_class_test.rb +3 -0
  29. data/test/acceptance/unexpected_invocation_test.rb +25 -0
  30. data/test/assertions.rb +6 -0
  31. data/test/integration/mini_test_test.rb +4 -18
  32. data/test/integration/test_unit_test.rb +2 -3
  33. data/test/test_helper.rb +6 -2
  34. data/test/test_runner.rb +22 -18
  35. data/test/unit/expectation_list_test.rb +11 -0
  36. data/test/unit/parameter_matchers/responds_with_test.rb +7 -0
  37. data/test/unit/receivers_test.rb +66 -0
  38. data/yard-templates/default/layout/html/google_analytics.erb +9 -9
  39. metadata +93 -109
  40. data/build-matrix.rb +0 -71
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: b1d70e16a4b44a20689afee9a18311ac4fd1fc74
4
+ data.tar.gz: f09339da5ed1062102aecb18a9461b89314b2fad
5
+ SHA512:
6
+ metadata.gz: ef120f8f4200434740018195cbada145b17ae893cb615cd45de7b75131acfc1712c0e8779b74a3d3591235382d4cc5babd432a2e78638b349730dd18dc628cf1
7
+ data.tar.gz: cc80f87dde614a0a06141edabbebc81696ffed1541349ecbcc9fd444888729130f66f9b219bc6e1fc4d9159f86e3db820e8c7fcaa8d801b98e4285f63f6bd3f4
data/README.md CHANGED
@@ -15,17 +15,17 @@ Install the latest version of the gem with the following command...
15
15
 
16
16
  $ gem install mocha
17
17
 
18
- Note: If you are intending to use Mocha with Test::Unit or MiniTest, you should only load Mocha *after* loading the relevant test library...
18
+ Note: If you are intending to use Mocha with Test::Unit or MiniTest, you should only setup Mocha *after* loading the relevant test library...
19
19
 
20
20
  require "test/unit"
21
21
  require "mocha/setup"
22
22
 
23
23
  #### Bundler
24
24
 
25
- If you're using Bundler, ensure the correct load order by not auto-requiring Mocha in the `Gemfile` and then load it later once you know the test library has been loaded...
25
+ If you're using Bundler, include Mocha in the `Gemfile` and then setup Mocha later once you know the test library has been loaded...
26
26
 
27
27
  # Gemfile
28
- gem "mocha", :require => false
28
+ gem "mocha"
29
29
 
30
30
  # Elsewhere after Bundler has loaded gems
31
31
  require "test/unit"
@@ -33,10 +33,10 @@ If you're using Bundler, ensure the correct load order by not auto-requiring Moc
33
33
 
34
34
  #### Rails
35
35
 
36
- If you're loading Mocha using Bundler within a Rails application, you should ensure Mocha is not auto-required (as above) and load Mocha manually e.g. at the bottom of your `test_helper.rb`.
36
+ If you're loading Mocha using Bundler within a Rails application, you should setup Mocha manually e.g. at the bottom of your `test_helper.rb`.
37
37
 
38
38
  # Gemfile in Rails app
39
- gem "mocha", :require => false
39
+ gem "mocha"
40
40
 
41
41
  # At bottom of test_helper.rb
42
42
  require "mocha/setup"
@@ -47,7 +47,10 @@ Install the Rails plugin...
47
47
 
48
48
  $ rails plugin install git://github.com/freerange/mocha.git
49
49
 
50
- Note: As of version 0.9.8, the Mocha plugin is not automatically loaded at plugin load time. Instead it must be manually loaded e.g. at the bottom of your `test_helper.rb`.
50
+ Note: As of version 0.9.8, the Mocha plugin is not automatically setup at plugin load time. Instead it must be manually setup e.g. at the bottom of your `test_helper.rb`.
51
+
52
+ # At bottom of test_helper.rb
53
+ require "mocha/setup"
51
54
 
52
55
  #### Know Issues
53
56
 
@@ -193,6 +196,18 @@ class OrderTest < Test::Unit::TestCase
193
196
  end
194
197
  ```
195
198
 
199
+ ### Thread safety
200
+
201
+ Mocha is currently *not* thread-safe. There are two main reasons for this: (a) in multi-threaded code Mocha exceptions may be raised in a thread other than the one which is running the test and thus a Mocha exception may not be correctly intercepted by Mocha exception handling code; and (b) partial mocking changes the state of objects in the `ObjectSpace` which is shared across all threads in the Ruby process and this access to what is effectively global state is not synchronized.
202
+
203
+ ### Expectation matching / invocation order
204
+
205
+ Stubs and expectations are basically the same thing. A stub is just an expectation of zero or more invocations. The `Expectation#stubs` method is syntactic sugar to make the intent of the test more explicit.
206
+
207
+ When a method is invoked on a mock object, the mock object searches through its expectations from newest to oldest to find one that matches the invocation. After the invocation, the matching expectation might stop matching further invocations.
208
+
209
+ See the [documentation](http://gofreerange.com/mocha/docs/Mocha/Mock.html) for `Mocha::Mock` for further details.
210
+
196
211
  ### Useful Links
197
212
 
198
213
  * [Official Documentation](http://gofreerange.com/mocha/docs/)
@@ -208,8 +223,13 @@ end
208
223
  ### Contributing
209
224
 
210
225
  * Fork the repository.
211
- * Make your changes in a branch (including tests).
212
- * Ensure all of the tests run against all supported versions of Test::Unit, MiniTest & Ruby by running ./build-matrix.rb (note that currently this makes a *lot* of assumptions about your local environment e.g. rbenv installed with specific Ruby versions).
226
+ * Make your changes in a branch.
227
+ * Add tests for new behaviour. Modify existing tests for changes to existing behaviour.
228
+ * Run `bin/build-matrix` from the root directory and ensure all the tests pass.
229
+ * This script depends on `rbenv` being installed.
230
+ * You must have all the ruby versions listed in `.travis.yml` under the `rvm` key installed (currently 1.8.7, 1.9.3 & 2.0.0).
231
+ * I use `rbenv-aliases` to alias the patch versions.
232
+ * Note that the build matrix takes quite a while to run.
213
233
  * Send us a pull request from your fork/branch.
214
234
 
215
235
  ### Contributors
data/RELEASE.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Release Notes
2
2
 
3
+ ## 1.0.0.alpha
4
+
5
+ * Assume 'mocha' has been required when requiring 'mocha/setup'.
6
+ * Provide shortcuts for integrating with specific test library i.e. `require 'mocha/test_unit'` or `require 'mocha/mini_test'`
7
+ as alternatives to `require 'mocha/setup'`.
8
+ * Do not automatically try to integrate with test libraries. Since the automatic test library integration functionality
9
+ requires the test library to be loaded and this doesn't usually happen until *after* the bundle is loaded, it makes things
10
+ simpler if we use `require 'mocha/setup'` to explicitly setup Mocha when we know the test library has been loaded. Fixes #146 & #155.
11
+ * Consider stubs on superclasses if none exist on primary receiver. Largely based on changes suggested by @ccutrer in #145.
12
+ Note: this may break existing tests which rely on the old behaviour. Stubbing a superclass method and then invoking that
13
+ method on a child class would previously cause an unexpected invocation error. By searching up through the inheritance
14
+ hierarchy for each of the delegate mock objects, we can provide more intuitive behaviour. Instead of an unexpected invocation
15
+ error, invoking the method on the child class will cause the stubbed method on the superclass to be used.
16
+ * Run the standard test suite against Ruby 2.1.0 in the build matrix.
17
+ * Avoid recursion when constructing unexpected invocation message. Fixes #168.
18
+ * Run integration tests against Ruby 2.0.0 with latest Test::Unit gem in the build matrix.
19
+ * Test::Unit is not available in Ruby v1.9.3 standard library, so remove it from the build matrix.
20
+ * Force use of Test::Unit runner, etc in relevant integration tests. Prior to this, I don't think we were really testing the
21
+ Mocha integration with Test::Unit much, because, although `TestUnitTest` was a subclass of `Test::Unit::TestCase`, the
22
+ important test case instances are the temporary ones built by `TestRunner#run_as_test` et al. Prior to this change, these
23
+ would only have used Test::Unit where MiniTest was not available *at all* i.e. only in early versions of Ruby and when the
24
+ MiniTest gem was not loaded.
25
+ * Reset environment variables between build matrix builds.
26
+ * Only activate integration with relevant test library for each of the integration tests.
27
+ * Include standard build combinations from Travis CI config i.e. builds using standard library versions of test libraries.
28
+ * Add explanation of method dispatch. Heavily based on the relevant jMock v1 documentation. Fixes #172.
29
+ * Make class_eval line number more accurate. This sets the line number as the line number of the `def` statement. Closes #169.
30
+ * Allow nesting of `responds_with` parameter matcher. Closes #166.
31
+ * Define `Mocha` module before it's referenced. The test helper defines a class `TestCase` within the `Mocha` module. When
32
+ running the tests inside the bundle, the `Mocha` module happens to be defined at this point. However when running the tests outside the bundle, it is not defined and so an exception is raised: `uninitialized constant Mocha (NameError)`. Fixes #163.
33
+ * Document lack of thread-safety. Fixes #154.
34
+ * Document how to use the build-matrix script. Fixes #160.
35
+ * Stubbing non-public method should use same visibility. This will probably break some existing tests that were somehow relying
36
+ on the stubbed method being public while the original method was protected or private. Fixes #150.
37
+ * Remove ruby version map from build matrix script. I'm using the `rbenv-aliases` plugin to alias minor versions to the
38
+ relevant patch version.
39
+ * Fix `build-matrix.rb` script. Also use `.travis.yml` to decide what combinations to run. This means we
40
+ can now simulate the Travis CI build locally and avoid duplication. Fixes #157.
41
+
42
+
3
43
  ## 0.14.0
4
44
 
5
45
  * Official support for MiniTest v5. All tests now pass on the continuous integration build.
@@ -0,0 +1,71 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'yaml'
4
+
5
+ def execute(*commands)
6
+ commands.each do |command|
7
+ system(command)
8
+ unless $?.success?
9
+ message = [
10
+ "Executing shell command failed.",
11
+ " Command: #{command}",
12
+ " Status: #{$?.exitstatus}"
13
+ ].join("\n")
14
+ raise message
15
+ end
16
+ end
17
+ end
18
+
19
+ def reset_bundle
20
+ execute(
21
+ "rm -rf .bundle/gems",
22
+ "rm -rf gemfiles/.bundle/gems",
23
+ "rm -f *.lock",
24
+ "rm -f gemfiles/*.lock"
25
+ )
26
+ end
27
+
28
+ def with_rbenv(command)
29
+ %{export PATH="$HOME/.rbenv/bin:$PATH"; eval "$(rbenv init -)"; #{command}}
30
+ end
31
+
32
+ def run(ruby_version, gemfile, task = "test")
33
+ ENV["RBENV_VERSION"] = ruby_version
34
+ ENV["BUNDLE_GEMFILE"] = gemfile
35
+ ENV["MOCHA_OPTIONS"] = "debug"
36
+ ENV["MOCHA_NO_DOCS"] = "true"
37
+ reset_bundle
38
+ execute(
39
+ with_rbenv("bundle install --gemfile=#{gemfile}"),
40
+ with_rbenv("bundle exec rake #{task}"),
41
+ )
42
+ end
43
+
44
+ travis_config = YAML.load(File.read('.travis.yml'))
45
+ build_configs = travis_config['matrix']['include']
46
+ travis_config['rvm'].each do |ruby_version|
47
+ travis_config['gemfile'].each do |gemfile|
48
+ travis_config['env'].each do |env|
49
+ build_configs << { 'rvm' => ruby_version, 'gemfile' => gemfile, 'env' => env }
50
+ end
51
+ end
52
+ end
53
+
54
+ build_configs.each do |config|
55
+ ruby_version = config['rvm']
56
+ gemfile = config['gemfile']
57
+ environment_variables = Hash[*config['env'].split.flat_map { |e| e.split('=') }]
58
+ original_environment_variables = {}
59
+ begin
60
+ environment_variables.each do |k, v|
61
+ original_environment_variables[k] = ENV[k]
62
+ ENV[k] = v
63
+ end
64
+ p [ruby_version, gemfile, environment_variables]
65
+ run(ruby_version, gemfile)
66
+ ensure
67
+ original_environment_variables.each do |k, v|
68
+ ENV[k] = v
69
+ end
70
+ end
71
+ end
@@ -1,8 +1 @@
1
1
  require 'mocha/version'
2
- require 'mocha/integration'
3
- require 'mocha/deprecation'
4
-
5
- Mocha::Deprecation.warning("Change `require 'mocha'` to `require 'mocha/setup'`.")
6
-
7
- require 'mocha/setup'
8
-
@@ -32,11 +32,11 @@ module Mocha
32
32
  end
33
33
 
34
34
  def define_new_method
35
- stubbee.class_eval(%{
35
+ stubbee.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
36
36
  def #{method}(*args, &block)
37
37
  self.class.any_instance.mocha.method_missing(:#{method}, *args, &block)
38
38
  end
39
- }, __FILE__, __LINE__)
39
+ CODE
40
40
  end
41
41
 
42
42
  def remove_new_method
@@ -54,11 +54,14 @@ module Mocha
54
54
  end
55
55
 
56
56
  def define_new_method
57
- stubbee.__metaclass__.class_eval(%{
57
+ stubbee.__metaclass__.class_eval(<<-CODE, __FILE__, __LINE__ + 1)
58
58
  def #{method}(*args, &block)
59
59
  mocha.method_missing(:#{method}, *args, &block)
60
60
  end
61
- }, __FILE__, __LINE__)
61
+ CODE
62
+ if @original_visibility
63
+ Module.instance_method(@original_visibility).bind(stubbee.__metaclass__).call(method)
64
+ end
62
65
  end
63
66
 
64
67
  def remove_new_method
@@ -0,0 +1,25 @@
1
+ module Mocha
2
+ module Detection
3
+ module MiniTest
4
+ def self.testcase
5
+ if defined?(::Minitest::Test)
6
+ ::Minitest::Test
7
+ elsif defined?(::MiniTest::Unit::TestCase)
8
+ ::MiniTest::Unit::TestCase
9
+ else
10
+ nil
11
+ end
12
+ end
13
+
14
+ def self.version
15
+ if defined?(::MiniTest::Unit::VERSION)
16
+ ::MiniTest::Unit::VERSION
17
+ elsif defined?(::Minitest::VERSION)
18
+ ::Minitest::VERSION
19
+ else
20
+ '0.0.0'
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,29 @@
1
+ module Mocha
2
+ module Detection
3
+ module TestUnit
4
+ def self.testcase
5
+ if defined?(::Test::Unit::TestCase) &&
6
+ !(defined?(::MiniTest::Unit::TestCase) && (::Test::Unit::TestCase < ::MiniTest::Unit::TestCase)) &&
7
+ !(defined?(::MiniTest::Spec) && (::Test::Unit::TestCase < ::MiniTest::Spec))
8
+ ::Test::Unit::TestCase
9
+ else
10
+ nil
11
+ end
12
+ end
13
+
14
+ def self.version
15
+ version = '1.0.0'
16
+ if testcase
17
+ begin
18
+ require 'test/unit/version'
19
+ rescue LoadError
20
+ end
21
+ if defined?(::Test::Unit::VERSION)
22
+ version = ::Test::Unit::VERSION
23
+ end
24
+ end
25
+ version
26
+ end
27
+ end
28
+ end
29
+ end
@@ -2,8 +2,8 @@ module Mocha
2
2
 
3
3
  class ExpectationList
4
4
 
5
- def initialize
6
- @expectations = []
5
+ def initialize(expectations = [])
6
+ @expectations = expectations
7
7
  end
8
8
 
9
9
  def add(expectation)
@@ -47,6 +47,10 @@ module Mocha
47
47
  @expectations.any?
48
48
  end
49
49
 
50
+ def +(other)
51
+ self.class.new(self.to_a + other.to_a)
52
+ end
53
+
50
54
  private
51
55
 
52
56
  def matching_expectations(method_name, *arguments)
@@ -6,8 +6,8 @@ module Mocha
6
6
  module Integration
7
7
  def self.activate
8
8
  if [Integration::TestUnit, Integration::MiniTest].map(&:activate).none?
9
- Deprecation.warning("Test::Unit or MiniTest must be loaded *before* Mocha.")
10
- Deprecation.warning("If you're integrating with a test library other than Test::Unit or MiniTest, you should use `require 'mocha/api'` instead of `require 'mocha'`.")
9
+ Deprecation.warning("Test::Unit or MiniTest must be loaded *before* `require 'mocha/setup'`.")
10
+ Deprecation.warning("If you're integrating with a test library other than Test::Unit or MiniTest, you should use `require 'mocha/api'` instead of `require 'mocha/setup'`.")
11
11
  end
12
12
  end
13
13
  end
@@ -1,5 +1,7 @@
1
1
  require 'mocha/debug'
2
2
 
3
+ require 'mocha/detection/mini_test'
4
+
3
5
  require 'mocha/integration/mini_test/nothing'
4
6
  require 'mocha/integration/mini_test/version_13'
5
7
  require 'mocha/integration/mini_test/version_140'
@@ -16,13 +18,8 @@ module Mocha
16
18
  module Integration
17
19
  module MiniTest
18
20
  def self.activate
19
- return false unless defined?(::MiniTest::Unit::TestCase)
20
-
21
- mini_test_version = begin
22
- Gem::Version.new(::MiniTest::Unit::VERSION)
23
- rescue LoadError
24
- Gem::Version.new('0.0.0')
25
- end
21
+ return false unless Detection::MiniTest.testcase
22
+ mini_test_version = Gem::Version.new(Detection::MiniTest.version)
26
23
 
27
24
  Debug.puts "Detected MiniTest version: #{mini_test_version}"
28
25
 
@@ -40,15 +37,13 @@ module Mocha
40
37
  MiniTest::Nothing
41
38
  ].detect { |m| m.applicable_to?(mini_test_version) }
42
39
 
43
- target = defined?(Minitest::Test) ? ::Minitest::Test : ::MiniTest::Unit::TestCase
40
+ target = Detection::MiniTest.testcase
44
41
  unless target < integration_module
45
42
  Debug.puts "Applying #{integration_module.description}"
46
43
  target.send(:include, integration_module)
47
44
  end
45
+ true
48
46
  end
49
- true
50
47
  end
51
48
  end
52
49
  end
53
-
54
-
@@ -1,5 +1,7 @@
1
1
  require 'mocha/debug'
2
2
 
3
+ require 'mocha/detection/test_unit'
4
+
3
5
  require 'mocha/integration/test_unit/nothing'
4
6
  require 'mocha/integration/test_unit/ruby_version_185_and_below'
5
7
  require 'mocha/integration/test_unit/ruby_version_186_and_above'
@@ -13,15 +15,8 @@ module Mocha
13
15
  module Integration
14
16
  module TestUnit
15
17
  def self.activate
16
- return false unless defined?(::Test::Unit::TestCase) && !(defined?(::MiniTest::Unit::TestCase) && (::Test::Unit::TestCase < ::MiniTest::Unit::TestCase)) && !(defined?(::MiniTest::Spec) && (::Test::Unit::TestCase < ::MiniTest::Spec))
17
-
18
- test_unit_version = begin
19
- require 'test/unit/version'
20
- Gem::Version.new(::Test::Unit::VERSION)
21
- rescue LoadError
22
- Gem::Version.new('1.0.0')
23
- end
24
-
18
+ return false unless Detection::TestUnit.testcase
19
+ test_unit_version = Gem::Version.new(Detection::TestUnit.version)
25
20
  ruby_version = Gem::Version.new(RUBY_VERSION.dup)
26
21
 
27
22
  Debug.puts "Detected Ruby version: #{ruby_version}"
@@ -0,0 +1,3 @@
1
+ require "mocha/integration/mini_test"
2
+
3
+ Mocha::Integration::MiniTest.activate
@@ -2,6 +2,7 @@ require 'metaclass'
2
2
  require 'mocha/expectation'
3
3
  require 'mocha/expectation_list'
4
4
  require 'mocha/names'
5
+ require 'mocha/receivers'
5
6
  require 'mocha/method_matcher'
6
7
  require 'mocha/parameters_matcher'
7
8
  require 'mocha/unexpected_invocation'
@@ -12,7 +13,59 @@ module Mocha
12
13
 
13
14
  # Traditional mock object.
14
15
  #
15
- # All methods return an {Expectation} which can be further modified by methods on {Expectation}.
16
+ # All methods return an {Expectation} which can be further modified by
17
+ # methods on {Expectation}.
18
+ #
19
+ # Stubs and expectations are basically the same thing. A stub is just an
20
+ # expectation of zero or more invocations. The {#stubs} method is syntactic
21
+ # sugar to make the intent of the test more explicit.
22
+ #
23
+ # When a method is invoked on a mock object, the mock object searches through
24
+ # its expectations from newest to oldest to find one that matches the
25
+ # invocation. After the invocation, the matching expectation might stop
26
+ # matching further invocations. For example, an +expects(:foo).once+
27
+ # expectation only matches once and will be ignored on future invocations
28
+ # while an +expects(:foo).at_least_once+ expectation will always be matched
29
+ # against invocations.
30
+ #
31
+ # This scheme allows you to:
32
+ #
33
+ # - Set up default stubs in your the +setup+ method of your test class and
34
+ # override some of those stubs in individual tests.
35
+ # - Set up different +once+ expectations for the same method with different
36
+ # action per invocation. However, it's better to use the
37
+ # {Expectation#returns} method with multiple arguments to do this, as
38
+ # described below.
39
+ #
40
+ # However, there are some possible "gotchas" caused by this scheme:
41
+ #
42
+ # - if you create an expectation and then a stub for the same method, the
43
+ # stub will always override the expectation and the expectation will never
44
+ # be met.
45
+ # - if you create a stub and then an expectation for the same method, the
46
+ # expectation will match, and when it stops matching the stub will be used
47
+ # instead, possibly masking test failures.
48
+ # - if you create different expectations for the same method, they will be
49
+ # invoked in the opposite order than that in which they were specified,
50
+ # rather than the same order.
51
+ #
52
+ # The best thing to do is not set up multiple expectations and stubs for the
53
+ # same method with exactly the same matchers. Instead, use the
54
+ # {Expectation#returns} method with multiple arguments to create multiple
55
+ # actions for a method. You can also chain multiple calls to
56
+ # {Expectation#returns} and {Expectation#raises} (along with syntactic sugar
57
+ # {Expectation#then} if desired).
58
+ #
59
+ # @example
60
+ # object = mock()
61
+ # object.stubs(:expected_method).returns(1, 2).then.raises(Exception)
62
+ # object.expected_method # => 1
63
+ # object.expected_method # => 2
64
+ # object.expected_method # => raises exception of class Exception1
65
+ #
66
+ # If you want to specify more complex ordering or order invocations across
67
+ # different mock objects, use the {Expectation#in_sequence} method to
68
+ # explicitly define a total or partial ordering of invocations.
16
69
  class Mock
17
70
 
18
71
  # Adds an expectation that the specified method must be called exactly once with any parameters.
@@ -194,12 +247,14 @@ module Mocha
194
247
  end
195
248
 
196
249
  # @private
197
- def initialize(mockery, name = nil, &block)
250
+ def initialize(mockery, name = nil, receiver = nil, &block)
198
251
  @mockery = mockery
199
252
  @name = name || DefaultName.new(self)
253
+ @receiver = receiver || DefaultReceiver.new(self)
200
254
  @expectations = ExpectationList.new
201
255
  @everything_stubbed = false
202
256
  @responder = nil
257
+ @unexpected_invocation = nil
203
258
  instance_eval(&block) if block
204
259
  end
205
260
 
@@ -223,18 +278,28 @@ module Mocha
223
278
  @everything_stubbed = true
224
279
  end
225
280
 
281
+ # @private
282
+ def all_expectations
283
+ @receiver.mocks.inject(ExpectationList.new) { |e, m| e + m.__expectations__ }
284
+ end
285
+
226
286
  # @private
227
287
  def method_missing(symbol, *arguments, &block)
228
288
  if @responder and not @responder.respond_to?(symbol)
229
289
  raise NoMethodError, "undefined method `#{symbol}' for #{self.mocha_inspect} which responds like #{@responder.mocha_inspect}"
230
290
  end
231
- if matching_expectation_allowing_invocation = @expectations.match_allowing_invocation(symbol, *arguments)
291
+ if matching_expectation_allowing_invocation = all_expectations.match_allowing_invocation(symbol, *arguments)
232
292
  matching_expectation_allowing_invocation.invoke(&block)
233
293
  else
234
- if (matching_expectation = @expectations.match(symbol, *arguments)) || (!matching_expectation && !@everything_stubbed)
235
- matching_expectation.invoke(&block) if matching_expectation
236
- message = UnexpectedInvocation.new(self, symbol, *arguments).to_s
237
- message << @mockery.mocha_inspect
294
+ if (matching_expectation = all_expectations.match(symbol, *arguments)) || (!matching_expectation && !@everything_stubbed)
295
+ if @unexpected_invocation.nil?
296
+ @unexpected_invocation = UnexpectedInvocation.new(self, symbol, *arguments)
297
+ matching_expectation.invoke(&block) if matching_expectation
298
+ message = @unexpected_invocation.full_description
299
+ message << @mockery.mocha_inspect
300
+ else
301
+ message = @unexpected_invocation.short_description
302
+ end
238
303
  raise ExpectationErrorFactory.build(message, caller)
239
304
  end
240
305
  end
@@ -249,7 +314,7 @@ module Mocha
249
314
  @responder.respond_to?(symbol)
250
315
  end
251
316
  else
252
- @everything_stubbed || @expectations.matches_method?(symbol)
317
+ @everything_stubbed || all_expectations.matches_method?(symbol)
253
318
  end
254
319
  end
255
320