rspec-mocks 2.14.0.rc1 → 2.14.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.
Files changed (35) hide show
  1. data/Changelog.md +25 -0
  2. data/features/message_expectations/allow_any_instance_of.feature +26 -0
  3. data/features/message_expectations/expect_any_instance_of.feature +27 -0
  4. data/features/message_expectations/expect_message_using_expect.feature +4 -0
  5. data/features/method_stubs/README.md +28 -2
  6. data/features/method_stubs/any_instance.feature +5 -1
  7. data/features/method_stubs/as_null_object.feature +6 -1
  8. data/features/method_stubs/simple_return_value_with_allow.feature +44 -0
  9. data/features/method_stubs/{simple_return_value.feature → simple_return_value_with_stub.feature} +0 -0
  10. data/features/method_stubs/stub_implementation.feature +23 -1
  11. data/lib/rspec/mocks.rb +35 -0
  12. data/lib/rspec/mocks/any_instance/chain.rb +27 -18
  13. data/lib/rspec/mocks/any_instance/expectation_chain.rb +1 -28
  14. data/lib/rspec/mocks/any_instance/recorder.rb +1 -2
  15. data/lib/rspec/mocks/any_instance/stub_chain.rb +2 -1
  16. data/lib/rspec/mocks/error_generator.rb +7 -0
  17. data/lib/rspec/mocks/extensions/marshal.rb +7 -7
  18. data/lib/rspec/mocks/matchers/receive.rb +5 -1
  19. data/lib/rspec/mocks/message_expectation.rb +142 -63
  20. data/lib/rspec/mocks/method_double.rb +0 -9
  21. data/lib/rspec/mocks/proxy.rb +0 -5
  22. data/lib/rspec/mocks/proxy_for_nil.rb +2 -1
  23. data/lib/rspec/mocks/syntax.rb +15 -5
  24. data/lib/rspec/mocks/test_double.rb +2 -2
  25. data/lib/rspec/mocks/version.rb +1 -1
  26. data/spec/rspec/mocks/any_instance_spec.rb +26 -0
  27. data/spec/rspec/mocks/block_return_value_spec.rb +18 -0
  28. data/spec/rspec/mocks/combining_implementation_instructions_spec.rb +205 -0
  29. data/spec/rspec/mocks/extensions/marshal_spec.rb +54 -0
  30. data/spec/rspec/mocks/matchers/receive_spec.rb +10 -0
  31. data/spec/rspec/mocks/mock_spec.rb +31 -3
  32. data/spec/rspec/mocks/partial_mock_spec.rb +3 -3
  33. data/spec/rspec/mocks/syntax_agnostic_message_matchers_spec.rb +81 -0
  34. data/spec/rspec/mocks/test_double_spec.rb +16 -6
  35. metadata +24 -9
@@ -1,3 +1,27 @@
1
+ ### 2.14.0 / 2013-07-06
2
+ [full changelog](http://github.com/rspec/rspec-mocks/compare/v2.14.0.rc1...v2.14.0)
3
+
4
+ Enhancements:
5
+
6
+ * Document test spies in the readme. (Adarsh Pandit)
7
+ * Add an `array_including` matcher. (Sam Phippen)
8
+ * Add a syntax-agnostic API for mocking or stubbing a method. This is
9
+ intended for use by libraries such as rspec-rails that need to mock
10
+ or stub a method, and work regardless of the syntax the user has
11
+ configured (Paul Annesley, Myron Marston and Sam Phippen).
12
+
13
+ Bug Fixes:
14
+
15
+ * Fix `double` so that it sets up passed stubs correctly regardless of
16
+ the configured syntax (Paul Annesley).
17
+ * Allow a block implementation to be used in combination with
18
+ `and_yield`, `and_raise`, `and_return` or `and_throw`. This got fixed
19
+ in 2.13.1 but failed to get merged into master for the 2.14.0.rc1
20
+ release (Myron Marston).
21
+ * `Marshal.dump` does not unnecessarily duplicate objects when rspec-mocks has
22
+ not been fully initialized. This could cause errors when using `spork` or
23
+ similar preloading gems (Andy Lindeman).
24
+
1
25
  ### 2.14.0.rc1 / 2013-05-27
2
26
  [full changelog](http://github.com/rspec/rspec-mocks/compare/v2.13.0...v2.14.0.rc1)
3
27
 
@@ -37,6 +61,7 @@ Bug fixes
37
61
  (Luke Imhoff, Jon Rowe)
38
62
  * Fix isolation of `allow_message_expectations_on_nil` (Jon Rowe)
39
63
  * Use inspect to format actual arguments on expectations in failure messages (#280, Ben Langfeld)
64
+ * Protect against improperly initialised test doubles (#293) (Joseph Shraibman and Jon Rowe)
40
65
 
41
66
  Deprecations
42
67
 
@@ -0,0 +1,26 @@
1
+ Feature: allow a message on any instance of a class
2
+
3
+ Use `allow_any_instance_of(Class).to receive` when you want to configure how
4
+ instances of the given class respond to a message without setting an
5
+ expectation that the message will be received.
6
+
7
+ Scenario: allowing a message on any instance of a class
8
+ Given a file named "example_spec.rb" with:
9
+ """ruby
10
+ describe "any_instance.should_receive" do
11
+ before do
12
+ allow_any_instance_of(Object).to receive(:foo).and_return(:return_value)
13
+ end
14
+
15
+ it "allows any instance of the class to receive the message" do
16
+ o = Object.new
17
+ expect(o.foo).to eq(:return_value)
18
+ end
19
+
20
+ it "passes even if no instances receive that message" do
21
+ o = Object.new
22
+ end
23
+ end
24
+ """
25
+ When I run `rspec example_spec.rb`
26
+ Then the examples should all pass
@@ -0,0 +1,27 @@
1
+ Feature: expect a message on any instance of a class
2
+
3
+ Use `expect_any_instance_of(Class).to receive` to set an expectation that one
4
+ (and only one) instance of a class receives a message before the example is
5
+ completed. The spec will fail if no instance receives a message.
6
+
7
+ Scenario: expect a message on any instance of a class
8
+ Given a file named "example_spec.rb" with:
9
+ """ruby
10
+ describe "expect_any_instance_of" do
11
+ before do
12
+ expect_any_instance_of(Object).to receive(:foo).and_return(:return_value)
13
+ end
14
+
15
+ it "verifies that one instance of the class receives the message" do
16
+ o = Object.new
17
+ expect(o.foo).to eq(:return_value)
18
+ end
19
+
20
+ it "fails unless an instance receives that message" do
21
+ o = Object.new
22
+ end
23
+ end
24
+ """
25
+ When I run `rspec example_spec.rb`
26
+ Then the output should contain "2 examples, 1 failure"
27
+ And the output should contain "1) expect_any_instance_of fails unless an instance receives that message"
@@ -4,6 +4,10 @@ Feature: expect message using `expect`
4
4
  `object` should receive the message `:message` before the example is
5
5
  completed.
6
6
 
7
+ Note: You can use `expect_any_instance_of` when you don't have a reference
8
+ to the object that receives a message in your test. For more information,
9
+ see the message_expectations/expect_any_instance_of feature.
10
+
7
11
  Scenario: expect a message
8
12
  Given a file named "spec/account_spec.rb" with:
9
13
  """ruby
@@ -3,7 +3,11 @@
3
3
  # create a double
4
4
  obj = double()
5
5
 
6
- # specify a return value
6
+ # specify a return value using `:expect` syntax
7
+ allow(obj).to receive(:message) { :value }
8
+ allow(obj).to receive(:message).and_return(:value)
9
+
10
+ # specify a return value using `:should` syntax
7
11
  obj.stub(:message) { :value }
8
12
  obj.stub(:message => :value)
9
13
  obj.stub(:message).and_return(:value)
@@ -14,18 +18,29 @@ block contents are evaluated lazily when the `obj` receives the
14
18
 
15
19
  ### Fake implementation
16
20
 
21
+ allow(obj).to receive(:message) do |arg1, arg2|
22
+ # set expectations about the args in this block
23
+ # and/or return value
24
+ end
25
+
17
26
  obj.stub(:message) do |arg1, arg2|
18
27
  # set expectations about the args in this block
19
- # and/or set a return value
28
+ # and/or return a value
20
29
  end
21
30
 
22
31
  ### Raising/Throwing
23
32
 
33
+ allow(obj).to receive(:message).and_raise("this error")
34
+ allow(obj).to receive(:message).and_throw(:this_symbol)
35
+
24
36
  obj.stub(:message).and_raise("this error")
25
37
  obj.stub(:message).and_throw(:this_symbol)
26
38
 
27
39
  You can also use the block format:
28
40
 
41
+ allow(obj).to receive(:message) { raise "this error" }
42
+ allow(obj).to receive(:message) { throw :this_symbol }
43
+
29
44
  obj.stub(:message) { raise "this error" }
30
45
  obj.stub(:message) { throw :this_symbol }
31
46
 
@@ -33,15 +48,26 @@ You can also use the block format:
33
48
 
34
49
  #### Explicit arguments
35
50
 
51
+ allow(obj).to receive(:message).with('an argument') { ... }
52
+
36
53
  obj.stub(:message).with('an argument') { ... }
37
54
  obj.stub(:message).with('more_than', 'one_argument') { ... }
38
55
 
39
56
  #### Argument matchers
40
57
 
58
+ allow(obj).to receive(:message).with(anything()) { ... }
59
+ allow(obj).to receive(:message).with(an_instance_of(Money)) { ... }
60
+ allow(obj).to receive(:message).with(hash_including(:a => 'b')) { ... }
61
+ allow(obj).to receive(:message).with(array_including(1,2,3)) { ... }
62
+ # or
63
+ allow(obj).to receive(:message).with(array_including([1,2,3])) { ... }
64
+
41
65
  obj.stub(:message).with(anything()) { ... }
42
66
  obj.stub(:message).with(an_instance_of(Money)) { ... }
43
67
  obj.stub(:message).with(hash_including(:a => 'b')) { ... }
44
68
 
45
69
  #### Regular expressions
46
70
 
71
+ allow(obj).to receive(:message).with(/abc/) { ... }
72
+
47
73
  obj.stub(:message).with(/abc/) { ... }
@@ -6,6 +6,10 @@ Feature: stub on any instance of a class
6
6
 
7
7
  Messages can be stubbed on any class, including those in Ruby's core library.
8
8
 
9
+ Note: You can use `allow_any_instance_of` when you don't have a reference
10
+ to the object that receives a message in your test. For more information,
11
+ see the message_expectations/allow_any_instance_of feature.
12
+
9
13
  Scenario: any_instance stub with a single return value
10
14
  Given a file named "example_spec.rb" with:
11
15
  """ruby
@@ -129,4 +133,4 @@ Feature: stub on any instance of a class
129
133
  end
130
134
  """
131
135
  When I run `rspec stub_chain_spec.rb`
132
- Then the examples should all pass
136
+ Then the examples should all pass
@@ -16,7 +16,12 @@ Feature: as_null_object
16
16
  null_object.should respond_to(:an_undefined_method)
17
17
  end
18
18
 
19
- it "allows explicit stubs" do
19
+ it "allows explicit stubs using expect syntax" do
20
+ allow(null_object).to receive(:foo) { "bar" }
21
+ expect(null_object.foo).to eq("bar")
22
+ end
23
+
24
+ it "allows explicit stubs using should syntax" do
20
25
  null_object.stub(:foo) { "bar" }
21
26
  null_object.foo.should eq("bar")
22
27
  end
@@ -0,0 +1,44 @@
1
+ Feature: allow with a simple return value
2
+
3
+ Use the `allow` method with the `receive` matcher on a test double or a real
4
+ object to tell the object to return a value (or values) in response to a given
5
+ message. Nothing happens if the message is never received.
6
+
7
+ Scenario: stub with no return value
8
+ Given a file named "example_spec.rb" with:
9
+ """ruby
10
+ describe "a stub with no return value specified" do
11
+ let(:collaborator) { double("collaborator") }
12
+
13
+ it "returns nil" do
14
+ allow(collaborator).to receive(:message)
15
+ expect(collaborator.message).to be(nil)
16
+ end
17
+ end
18
+ """
19
+ When I run `rspec example_spec.rb`
20
+ Then the examples should all pass
21
+
22
+ Scenario: stubs with return values
23
+ Given a file named "example_spec.rb" with:
24
+ """ruby
25
+ describe "a stub with a return value" do
26
+ context "specified in a block" do
27
+ it "returns the specified value" do
28
+ collaborator = double("collaborator")
29
+ allow(collaborator).to receive(:message) { :value }
30
+ expect(collaborator.message).to eq(:value)
31
+ end
32
+ end
33
+
34
+ context "specified with #and_return" do
35
+ it "returns the specified value" do
36
+ collaborator = double("collaborator")
37
+ allow(collaborator).to receive(:message).and_return(:value)
38
+ expect(collaborator.message).to eq(:value)
39
+ end
40
+ end
41
+ end
42
+ """
43
+ When I run `rspec example_spec.rb`
44
+ Then the examples should all pass
@@ -3,7 +3,29 @@ Feature: stub with substitute implementation
3
3
  You can stub an implementation of a method (a.k.a. fake) by passing a block
4
4
  to the `stub` method.
5
5
 
6
- Scenario: stub implementation
6
+ Scenario: stub implementation using `expect` syntax
7
+ Given a file named "stub_implementation_spec.rb" with:
8
+ """ruby
9
+ describe "a stubbed implementation" do
10
+ it "works" do
11
+ object = Object.new
12
+ allow(object).to receive(:foo) do |arg|
13
+ if arg == :this
14
+ "got this"
15
+ elsif arg == :that
16
+ "got that"
17
+ end
18
+ end
19
+
20
+ expect(object.foo(:this)).to eq("got this")
21
+ expect(object.foo(:that)).to eq("got that")
22
+ end
23
+ end
24
+ """
25
+ When I run `rspec stub_implementation_spec.rb`
26
+ Then the output should contain "1 example, 0 failures"
27
+
28
+ Scenario: stub implementation using `should` syntax
7
29
  Given a file named "stub_implementation_spec.rb" with:
8
30
  """ruby
9
31
  describe "a stubbed implementation" do
@@ -30,6 +30,41 @@ module RSpec
30
30
  space.any_instance_recorder_for(klass)
31
31
  end
32
32
 
33
+ # Adds an allowance (stub) on `subject`
34
+ #
35
+ # @param subject the subject to which the message will be added
36
+ # @param message a symbol, representing the message that will be
37
+ # added.
38
+ # @param opts a hash of options, :expected_from is used to set the
39
+ # original call site
40
+ # @param block an optional implementation for the allowance
41
+ #
42
+ # @example Defines the implementation of `foo` on `bar`, using the passed block
43
+ # x = 0
44
+ # RSpec::Mocks.allow_message(bar, :foo) { x += 1 }
45
+ def allow_message(subject, message, opts={}, &block)
46
+ orig_caller = opts.fetch(:expected_from) { caller(1)[0] }
47
+ ::RSpec::Mocks.proxy_for(subject).
48
+ add_stub(orig_caller, message.to_sym, opts, &block)
49
+ end
50
+
51
+ # Sets a message expectation on `subject`.
52
+ # @param subject the subject on which the message will be expected
53
+ # @param message a symbol, representing the message that will be
54
+ # expected.
55
+ # @param opts a hash of options, :expected_from is used to set the
56
+ # original call site
57
+ # @param block an optional implementation for the expectation
58
+ #
59
+ # @example Expect the message `foo` to receive `bar`, then call it
60
+ # RSpec::Mocks.expect_message(bar, :foo)
61
+ # bar.foo
62
+ def expect_message(subject, message, opts={}, &block)
63
+ orig_caller = opts.fetch(:expected_from) { caller(1)[0] }
64
+ ::RSpec::Mocks.proxy_for(subject).
65
+ add_message_expectation(orig_caller, message.to_sym, opts, &block)
66
+ end
67
+
33
68
  # @api private
34
69
  KERNEL_METHOD_METHOD = ::Kernel.instance_method(:method)
35
70
 
@@ -7,9 +7,7 @@ module RSpec
7
7
  @expectation_block = block
8
8
  end
9
9
 
10
- class << self
11
- private
12
-
10
+ module Customizations
13
11
  # @macro [attach] record
14
12
  # @method $1(*args, &block)
15
13
  # Records the `$1` message for playback against an instance that
@@ -17,29 +15,31 @@ module RSpec
17
15
  #
18
16
  # @see RSpec::Mocks::MessageExpectation#$1
19
17
  #
20
- def record(method_name)
18
+ def self.record(method_name)
21
19
  class_eval(<<-EOM, __FILE__, __LINE__ + 1)
22
20
  def #{method_name}(*args, &block)
23
21
  record(:#{method_name}, *args, &block)
24
22
  end
25
23
  EOM
26
24
  end
25
+
26
+ record :and_return
27
+ record :and_raise
28
+ record :and_throw
29
+ record :and_yield
30
+ record :and_call_original
31
+ record :with
32
+ record :once
33
+ record :twice
34
+ record :any_number_of_times
35
+ record :exactly
36
+ record :times
37
+ record :never
38
+ record :at_least
39
+ record :at_most
27
40
  end
28
41
 
29
- record :and_return
30
- record :and_raise
31
- record :and_throw
32
- record :and_yield
33
- record :and_call_original
34
- record :with
35
- record :once
36
- record :twice
37
- record :any_number_of_times
38
- record :exactly
39
- record :times
40
- record :never
41
- record :at_least
42
- record :at_most
42
+ include Customizations
43
43
 
44
44
  # @private
45
45
  def playback!(instance)
@@ -63,8 +63,17 @@ module RSpec
63
63
  @expectation_fulfilled = true
64
64
  end
65
65
 
66
+ def never
67
+ ErrorGenerator.raise_double_negation_error("expect_any_instance_of(MyClass)") if negated?
68
+ super
69
+ end
70
+
66
71
  private
67
72
 
73
+ def negated?
74
+ messages.any? { |(message, *_), _| message.to_sym == :never }
75
+ end
76
+
68
77
  def messages
69
78
  @messages ||= []
70
79
  end
@@ -36,34 +36,7 @@ module RSpec
36
36
  }
37
37
  end
38
38
  end
39
-
40
- # @api private
41
- class NegativeExpectationChain < ExpectationChain
42
- # `should_not_receive` causes a failure at the point in time the
43
- # message is wrongly received, rather than during `rspec_verify`
44
- # at the end of an example. Thus, we should always consider a
45
- # negative expectation fulfilled for the purposes of end-of-example
46
- # verification (which is where this is used).
47
- def expectation_fulfilled?
48
- true
49
- end
50
-
51
- private
52
-
53
- def create_message_expectation_on(instance)
54
- proxy = ::RSpec::Mocks.proxy_for(instance)
55
- expected_from = IGNORED_BACKTRACE_LINE
56
- proxy.add_negative_message_expectation(expected_from, *@expectation_args, &@expectation_block)
57
- end
58
-
59
- def invocation_order
60
- @invocation_order ||= {
61
- :with => [nil],
62
- :and_return => [:with, nil],
63
- :and_raise => [:with, nil]
64
- }
65
- end
66
- end
67
39
  end
68
40
  end
69
41
  end
42
+
@@ -60,8 +60,7 @@ module RSpec
60
60
  end
61
61
 
62
62
  def should_not_receive(method_name, &block)
63
- observe!(method_name)
64
- message_chains.add(method_name, NegativeExpectationChain.new(method_name, &block))
63
+ should_receive(method_name, &block).never
65
64
  end
66
65
 
67
66
  # Removes any previously recorded stubs, stub_chains or message