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.
- checksums.yaml +7 -0
- data/README.md +28 -8
- data/RELEASE.md +40 -0
- data/bin/build-matrix +71 -0
- data/lib/mocha.rb +0 -7
- data/lib/mocha/any_instance_method.rb +2 -2
- data/lib/mocha/class_method.rb +5 -2
- data/lib/mocha/detection/mini_test.rb +25 -0
- data/lib/mocha/detection/test_unit.rb +29 -0
- data/lib/mocha/expectation_list.rb +6 -2
- data/lib/mocha/integration.rb +2 -2
- data/lib/mocha/integration/mini_test.rb +6 -11
- data/lib/mocha/integration/test_unit.rb +4 -9
- data/lib/mocha/mini_test.rb +3 -0
- data/lib/mocha/mock.rb +73 -8
- data/lib/mocha/mockery.rb +3 -2
- data/lib/mocha/object_methods.rb +4 -2
- data/lib/mocha/parameter_matchers/responds_with.rb +1 -1
- data/lib/mocha/receivers.rb +49 -0
- data/lib/mocha/setup.rb +0 -1
- data/lib/mocha/test_unit.rb +3 -0
- data/lib/mocha/unexpected_invocation.rb +10 -5
- data/lib/mocha/version.rb +1 -1
- data/test/acceptance/stub_any_instance_method_defined_on_superclass_test.rb +34 -0
- data/test/acceptance/stub_any_instance_method_test.rb +1 -0
- data/test/acceptance/stub_class_method_defined_on_class_test.rb +3 -0
- data/test/acceptance/stub_class_method_defined_on_superclass_test.rb +40 -3
- data/test/acceptance/stub_instance_method_defined_on_class_test.rb +3 -0
- data/test/acceptance/unexpected_invocation_test.rb +25 -0
- data/test/assertions.rb +6 -0
- data/test/integration/mini_test_test.rb +4 -18
- data/test/integration/test_unit_test.rb +2 -3
- data/test/test_helper.rb +6 -2
- data/test/test_runner.rb +22 -18
- data/test/unit/expectation_list_test.rb +11 -0
- data/test/unit/parameter_matchers/responds_with_test.rb +7 -0
- data/test/unit/receivers_test.rb +66 -0
- data/yard-templates/default/layout/html/google_analytics.erb +9 -9
- metadata +93 -109
- data/build-matrix.rb +0 -71
checksums.yaml
ADDED
@@ -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
|
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,
|
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"
|
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
|
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"
|
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
|
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
|
212
|
-
*
|
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.
|
data/bin/build-matrix
ADDED
@@ -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
|
data/lib/mocha.rb
CHANGED
@@ -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
|
-
|
39
|
+
CODE
|
40
40
|
end
|
41
41
|
|
42
42
|
def remove_new_method
|
data/lib/mocha/class_method.rb
CHANGED
@@ -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
|
-
|
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)
|
data/lib/mocha/integration.rb
CHANGED
@@ -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*
|
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
|
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 =
|
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
|
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}"
|
data/lib/mocha/mock.rb
CHANGED
@@ -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
|
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 =
|
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 =
|
235
|
-
|
236
|
-
|
237
|
-
|
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 ||
|
317
|
+
@everything_stubbed || all_expectations.matches_method?(symbol)
|
253
318
|
end
|
254
319
|
end
|
255
320
|
|