rspec-mocks 2.6.0 → 2.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.
Files changed (69) hide show
  1. data/README.md +0 -19
  2. data/features/Scope.md +17 -0
  3. data/features/argument_matchers/README.md +27 -0
  4. data/features/argument_matchers/explicit.feature +60 -0
  5. data/features/argument_matchers/general_matchers.feature +85 -0
  6. data/features/argument_matchers/type_matchers.feature +25 -0
  7. data/features/message_expectations/any_instance.feature +2 -0
  8. data/features/message_expectations/receive_counts.feature +209 -0
  9. data/features/method_stubs/README.md +6 -10
  10. data/features/method_stubs/any_instance.feature +111 -1
  11. data/features/method_stubs/simple_return_value.feature +34 -25
  12. data/features/method_stubs/stub_implementation.feature +1 -1
  13. data/features/method_stubs/to_ary.feature +12 -10
  14. data/features/support/env.rb +1 -1
  15. data/lib/rspec/mocks/any_instance/chain.rb +49 -0
  16. data/lib/rspec/mocks/any_instance/expectation_chain.rb +33 -0
  17. data/lib/rspec/mocks/any_instance/message_chains.rb +48 -0
  18. data/lib/rspec/mocks/any_instance/recorder.rb +168 -0
  19. data/lib/rspec/mocks/any_instance/stub_chain.rb +35 -0
  20. data/lib/rspec/mocks/any_instance/stub_chain_chain.rb +34 -0
  21. data/lib/rspec/mocks/any_instance.rb +8 -235
  22. data/lib/rspec/mocks/argument_expectation.rb +1 -1
  23. data/lib/rspec/mocks/argument_matchers.rb +19 -25
  24. data/lib/rspec/mocks/extensions/marshal.rb +1 -1
  25. data/lib/rspec/mocks/extensions/psych.rb +1 -1
  26. data/lib/rspec/mocks/message_expectation.rb +7 -4
  27. data/lib/rspec/mocks/methods.rb +6 -8
  28. data/lib/rspec/mocks/mock.rb +2 -2
  29. data/lib/rspec/mocks/proxy.rb +4 -4
  30. data/lib/rspec/mocks/version.rb +4 -4
  31. data/lib/rspec/mocks.rb +13 -14
  32. data/spec/rspec/mocks/any_instance/message_chains_spec.rb +40 -0
  33. data/spec/rspec/mocks/any_instance_spec.rb +219 -24
  34. data/spec/rspec/mocks/any_number_of_times_spec.rb +2 -2
  35. data/spec/rspec/mocks/argument_expectation_spec.rb +15 -3
  36. data/spec/rspec/mocks/at_least_spec.rb +1 -1
  37. data/spec/rspec/mocks/at_most_spec.rb +1 -1
  38. data/spec/rspec/mocks/bug_report_10263_spec.rb +1 -1
  39. data/spec/rspec/mocks/bug_report_7611_spec.rb +1 -1
  40. data/spec/rspec/mocks/bug_report_8165_spec.rb +2 -2
  41. data/spec/rspec/mocks/bug_report_957_spec.rb +2 -2
  42. data/spec/rspec/mocks/failing_argument_matchers_spec.rb +1 -1
  43. data/spec/rspec/mocks/hash_including_matcher_spec.rb +11 -11
  44. data/spec/rspec/mocks/hash_not_including_matcher_spec.rb +6 -6
  45. data/spec/rspec/mocks/mock_spec.rb +49 -43
  46. data/spec/rspec/mocks/multiple_return_value_spec.rb +26 -26
  47. data/spec/rspec/mocks/partial_mock_spec.rb +3 -2
  48. data/spec/rspec/mocks/partial_mock_using_mocks_directly_spec.rb +1 -0
  49. data/spec/rspec/mocks/passing_argument_matchers_spec.rb +3 -3
  50. data/spec/rspec/mocks/precise_counts_spec.rb +1 -1
  51. data/spec/rspec/mocks/serialization_spec.rb +7 -2
  52. data/spec/rspec/mocks/stub_implementation_spec.rb +6 -6
  53. data/spec/rspec/mocks/stub_spec.rb +6 -6
  54. data/spec/rspec/mocks/to_ary_spec.rb +9 -0
  55. metadata +45 -42
  56. data/.autotest +0 -7
  57. data/.document +0 -5
  58. data/.gitignore +0 -10
  59. data/.travis.yml +0 -7
  60. data/Gemfile +0 -40
  61. data/Guardfile +0 -8
  62. data/License.txt +0 -22
  63. data/Rakefile +0 -75
  64. data/autotest/discover.rb +0 -1
  65. data/cucumber.yml +0 -2
  66. data/features/.nav +0 -17
  67. data/features/Changelog.md +0 -80
  68. data/rspec-mocks.gemspec +0 -25
  69. data/specs.watchr +0 -57
@@ -1,55 +1,64 @@
1
1
  Feature: stub with a simple return value
2
2
 
3
3
  Use the `stub` method on a test double or a real object to tell the object to
4
- return a value (or values) in response to a given message. If the message is
5
- never received, nothing happens.
4
+ return a value (or values) in response to a given message. Nothing happens if
5
+ the message is never received.
6
6
 
7
- Scenario: simple stub with no return value
7
+ Scenario: stub with no return value
8
8
  Given a file named "example_spec.rb" with:
9
9
  """
10
- describe "a simple stub with no return value specified" do
11
- let(:receiver) { double("receiver") }
10
+ describe "a stub with no return value specified" do
11
+ let(:collaborator) { double("collaborator") }
12
12
 
13
13
  it "returns nil" do
14
- receiver.stub(:message)
15
- receiver.message.should be(nil)
16
- end
17
-
18
- it "quietly carries on when not called" do
19
- receiver.stub(:message)
14
+ collaborator.stub(:message)
15
+ collaborator.message.should be(nil)
20
16
  end
21
17
  end
22
18
  """
23
19
  When I run `rspec example_spec.rb`
24
- Then the output should contain "0 failures"
20
+ Then the examples should all pass
25
21
 
26
- Scenario: single return value
22
+ Scenario: stubs with return values
27
23
  Given a file named "example_spec.rb" with:
28
24
  """
29
- describe "a simple stub with a return value" do
25
+ describe "a stub with a return value" do
30
26
  context "specified in a block" do
31
27
  it "returns the specified value" do
32
- receiver = double("receiver")
33
- receiver.stub(:message) { :return_value }
34
- receiver.message.should eq(:return_value)
28
+ collaborator = double("collaborator")
29
+ collaborator.stub(:message) { :value }
30
+ collaborator.message.should 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
+ collaborator.stub(:message).and_return(:value)
38
+ collaborator.message.should eq(:value)
35
39
  end
36
40
  end
37
41
 
38
- context "specified in the double declaration" do
42
+ context "specified with a hash passed to #stub" do
39
43
  it "returns the specified value" do
40
- receiver = double("receiver", :message => :return_value)
41
- receiver.message.should eq(:return_value)
44
+ collaborator = double("collaborator")
45
+ collaborator.stub(:message_1 => :value_1, :message_2 => :value_2)
46
+ collaborator.message_1.should eq(:value_1)
47
+ collaborator.message_2.should eq(:value_2)
42
48
  end
43
49
  end
44
50
 
45
- context "specified with and_return" do
51
+ context "specified with a hash passed to #double" do
46
52
  it "returns the specified value" do
47
- receiver = double("receiver")
48
- receiver.stub(:message).and_return(:return_value)
49
- receiver.message.should eq(:return_value)
53
+ collaborator = double("collaborator",
54
+ :message_1 => :value_1,
55
+ :message_2 => :value_2
56
+ )
57
+ collaborator.message_1.should eq(:value_1)
58
+ collaborator.message_2.should eq(:value_2)
50
59
  end
51
60
  end
52
61
  end
53
62
  """
54
63
  When I run `rspec example_spec.rb`
55
- Then the output should contain "0 failures"
64
+ Then the examples should all pass
@@ -1,7 +1,7 @@
1
1
  Feature: stub with substitute implementation
2
2
 
3
3
  You can stub an implementation of a method (a.k.a. fake) by passing a block
4
- to the stub() method.
4
+ to the `stub` method.
5
5
 
6
6
  Scenario: stub implementation
7
7
  Given a file named "stub_implementation_spec.rb" with:
@@ -1,22 +1,24 @@
1
1
  Feature: double handling to_ary
2
2
 
3
- Ruby implicitly sends to_ary to any objects in an Array when the array
4
- receives `flatten`:
3
+ Ruby implicitly sends `to_ary` to all of the objects in an `Array` when the
4
+ array receives `flatten`:
5
5
 
6
6
  [obj].flatten # => obj.to_ary
7
7
 
8
+ If `to_ary` raises a `NoMethodError`, Ruby sees that the object can not be coerced
9
+ into an array and moves on to the next object.
10
+
8
11
  To support this, an RSpec double will raise a NoMethodError when it receives
9
- `to_ary`, unless that method is explicitly stubbed.
12
+ `to_ary` _even if it is set `as_null_object`_, unless `to_ary` is explicitly
13
+ stubbed.
10
14
 
11
15
  Scenario: double receiving to_ary
12
16
  Given a file named "example.rb" with:
13
17
  """
14
- describe "a double receiving to_ary" do
18
+ describe "#to_ary" do
15
19
  shared_examples "to_ary" do
16
- it "returns nil" do
17
- expect do
18
- obj.to_ary.should be_nil
19
- end.to raise_error(NoMethodError)
20
+ it "raises a NoMethodError" do
21
+ expect { obj.to_ary }.to raise_error(NoMethodError)
20
22
  end
21
23
 
22
24
  it "can be overridden with a stub" do
@@ -30,12 +32,12 @@ Feature: double handling to_ary
30
32
  end
31
33
  end
32
34
 
33
- context "double as_null_object" do
35
+ context "sent to a double as_null_object" do
34
36
  let(:obj) { double('obj').as_null_object }
35
37
  include_examples "to_ary"
36
38
  end
37
39
 
38
- context "double without as_null_object" do
40
+ context "sent to a double without as_null_object" do
39
41
  let(:obj) { double('obj') }
40
42
  include_examples "to_ary"
41
43
  end
@@ -2,5 +2,5 @@ require 'aruba/cucumber'
2
2
  require 'rspec/expectations'
3
3
 
4
4
  Before do
5
- @aruba_timeout_seconds = 3
5
+ RUBY_PLATFORM =~ /java/ ? @aruba_timeout_seconds = 60 : @aruba_timeout_seconds = 5
6
6
  end
@@ -0,0 +1,49 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ class Chain
5
+ [
6
+ :with, :and_return, :and_raise, :and_yield,
7
+ :once, :twice, :any_number_of_times,
8
+ :exactly, :times, :never,
9
+ :at_least, :at_most
10
+ ].each do |method_name|
11
+ class_eval(<<-EOM, __FILE__, __LINE__)
12
+ def #{method_name}(*args, &block)
13
+ record(:#{method_name}, *args, &block)
14
+ end
15
+ EOM
16
+ end
17
+
18
+ def playback!(instance)
19
+ messages.inject(instance) do |_instance, message|
20
+ _instance.__send__(*message.first, &message.last)
21
+ end
22
+ end
23
+
24
+ def constrained_to_any_of?(*constraints)
25
+ constraints.any? do |constraint|
26
+ messages.any? do |message|
27
+ message.first.first == constraint
28
+ end
29
+ end
30
+ end
31
+
32
+ private
33
+ def messages
34
+ @messages ||= []
35
+ end
36
+
37
+ def last_message
38
+ messages.last.first.first unless messages.empty?
39
+ end
40
+
41
+ def record(rspec_method_name, *args, &block)
42
+ verify_invocation_order(rspec_method_name, *args, &block)
43
+ messages << [args.unshift(rspec_method_name), block]
44
+ self
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,33 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ class ExpectationChain < Chain
5
+ def initialize(*args, &block)
6
+ record(:should_receive, *args, &block)
7
+ @expectation_fulfilled = false
8
+ end
9
+
10
+ def invocation_order
11
+ @invocation_order ||= {
12
+ :should_receive => [nil],
13
+ :with => [:should_receive],
14
+ :and_return => [:with, :should_receive],
15
+ :and_raise => [:with, :should_receive]
16
+ }
17
+ end
18
+
19
+ def expectation_fulfilled!
20
+ @expectation_fulfilled = true
21
+ end
22
+
23
+ def expectation_fulfilled?
24
+ @expectation_fulfilled || constrained_to_any_of?(:never, :any_number_of_times)
25
+ end
26
+
27
+ private
28
+ def verify_invocation_order(rspec_method_name, *args, &block)
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,48 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ class MessageChains < Hash
5
+ def add(method_name, chain)
6
+ (self[method_name] ||= []) << chain
7
+ end
8
+
9
+ def remove_stub_chains_for!(method_name)
10
+ chains = self[method_name]
11
+ chains.reject! { |chain| chain.is_a?(StubChain) || chain.is_a?(StubChainChain) }
12
+ end
13
+
14
+ def has_expectation?(method_name)
15
+ !!self[method_name].find{|chain| chain.is_a?(ExpectationChain)}
16
+ end
17
+
18
+ def each_expectation_fulfilled?
19
+ self.all? do |method_name, chains|
20
+ chains.all? { |chain| chain.expectation_fulfilled? }
21
+ end
22
+ end
23
+
24
+ def unfulfilled_expectations
25
+ self.map do |method_name, chains|
26
+ method_name.to_s if chains.last.is_a?(ExpectationChain) unless chains.last.expectation_fulfilled?
27
+ end.compact
28
+ end
29
+
30
+ def received_expected_message!(method_name)
31
+ self[method_name].each do |chain|
32
+ chain.expectation_fulfilled!
33
+ end
34
+ end
35
+
36
+ def playback!(instance, method_name)
37
+ self[method_name].each do |chain|
38
+ @instance_with_expectation = instance if instance.is_a?(ExpectationChain) && !@instance_with_expectation
39
+ if instance.is_a?(ExpectationChain) && !@instance_with_expectation.equal?(instance)
40
+ raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
41
+ end
42
+ chain.playback!(instance)
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,168 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ class Recorder
5
+ attr_reader :message_chains
6
+ def initialize(klass)
7
+ @message_chains = MessageChains.new
8
+ @observed_methods = []
9
+ @played_methods = {}
10
+ @klass = klass
11
+ @expectation_set = false
12
+ end
13
+
14
+ def unstub(method_name)
15
+ unless @observed_methods.include?(method_name.to_sym)
16
+ raise RSpec::Mocks::MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
17
+ end
18
+ message_chains.remove_stub_chains_for!(method_name)
19
+ stop_observing!(method_name) unless message_chains.has_expectation?(method_name)
20
+ end
21
+
22
+ def stub(method_name_or_method_map, *args, &block)
23
+ if method_name_or_method_map.is_a?(Hash)
24
+ method_map = method_name_or_method_map
25
+ method_map.each do |method_name, return_value|
26
+ observe!(method_name)
27
+ message_chains.add(method_name, chain = StubChain.new(method_name))
28
+ chain.and_return(return_value)
29
+ end
30
+ method_map
31
+ else
32
+ method_name = method_name_or_method_map
33
+ observe!(method_name)
34
+ message_chains.add(method_name, chain = StubChain.new(method_name, *args, &block))
35
+ chain
36
+ end
37
+ end
38
+
39
+ def stub!(*)
40
+ raise "stub! is not supported on any_instance. Use stub instead."
41
+ end
42
+
43
+ def stub_chain(method_name_or_string_chain, *args, &block)
44
+ if period_separated_method_chain?(method_name_or_string_chain)
45
+ first_method_name = method_name_or_string_chain.split('.').first.to_sym
46
+ else
47
+ first_method_name = method_name_or_string_chain
48
+ end
49
+ observe!(first_method_name)
50
+ message_chains.add(first_method_name, chain = StubChainChain.new(method_name_or_string_chain, *args, &block))
51
+ chain
52
+ end
53
+
54
+ def should_receive(method_name, *args, &block)
55
+ observe!(method_name)
56
+ @expectation_set = true
57
+ message_chains.add(method_name, chain = ExpectationChain.new(method_name, *args, &block))
58
+ chain
59
+ end
60
+
61
+ def stop_all_observation!
62
+ @observed_methods.each do |method_name|
63
+ restore_method!(method_name)
64
+ end
65
+ end
66
+
67
+ def playback!(instance, method_name)
68
+ RSpec::Mocks::space.add(instance)
69
+ message_chains.playback!(instance, method_name)
70
+ @played_methods[method_name] = instance
71
+ received_expected_message!(method_name) if message_chains.has_expectation?(method_name)
72
+ end
73
+
74
+ def instance_that_received(method_name)
75
+ @played_methods[method_name]
76
+ end
77
+
78
+ def verify
79
+ if @expectation_set && !message_chains.each_expectation_fulfilled?
80
+ raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{message_chains.unfulfilled_expectations.sort.join(', ')}"
81
+ end
82
+ end
83
+
84
+ private
85
+ def period_separated_method_chain?(method_name)
86
+ method_name.is_a?(String) && method_name.include?('.')
87
+ end
88
+
89
+ def received_expected_message!(method_name)
90
+ message_chains.received_expected_message!(method_name)
91
+ restore_method!(method_name)
92
+ mark_invoked!(method_name)
93
+ end
94
+
95
+ def restore_method!(method_name)
96
+ if public_protected_or_private_method_defined?(build_alias_method_name(method_name))
97
+ restore_original_method!(method_name)
98
+ else
99
+ remove_dummy_method!(method_name)
100
+ end
101
+ end
102
+
103
+ def build_alias_method_name(method_name)
104
+ "__#{method_name}_without_any_instance__"
105
+ end
106
+
107
+ def restore_original_method!(method_name)
108
+ alias_method_name = build_alias_method_name(method_name)
109
+ @klass.class_eval do
110
+ remove_method method_name
111
+ alias_method method_name, alias_method_name
112
+ remove_method alias_method_name
113
+ end
114
+ end
115
+
116
+ def remove_dummy_method!(method_name)
117
+ @klass.class_eval do
118
+ remove_method method_name
119
+ end
120
+ end
121
+
122
+ def backup_method!(method_name)
123
+ alias_method_name = build_alias_method_name(method_name)
124
+ @klass.class_eval do
125
+ alias_method alias_method_name, method_name
126
+ end if public_protected_or_private_method_defined?(method_name)
127
+ end
128
+
129
+ def public_protected_or_private_method_defined?(method_name)
130
+ @klass.method_defined?(method_name) || @klass.private_method_defined?(method_name)
131
+ end
132
+
133
+ def stop_observing!(method_name)
134
+ restore_method!(method_name)
135
+ @observed_methods.delete(method_name)
136
+ end
137
+
138
+ def already_observing?(method_name)
139
+ @observed_methods.include?(method_name)
140
+ end
141
+
142
+ def observe!(method_name)
143
+ stop_observing!(method_name) if already_observing?(method_name)
144
+ @observed_methods << method_name
145
+ backup_method!(method_name)
146
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
147
+ def #{method_name}(*args, &blk)
148
+ self.class.__recorder.playback!(self, :#{method_name})
149
+ self.__send__(:#{method_name}, *args, &blk)
150
+ end
151
+ EOM
152
+ end
153
+
154
+ def mark_invoked!(method_name)
155
+ backup_method!(method_name)
156
+ @klass.class_eval(<<-EOM, __FILE__, __LINE__)
157
+ def #{method_name}(*args, &blk)
158
+ method_name = :#{method_name}
159
+ current_instance = self
160
+ invoked_instance = self.class.__recorder.instance_that_received(method_name)
161
+ raise RSpec::Mocks::MockExpectationError, "The message '#{method_name}' was received by \#{self.inspect} but has already been received by \#{invoked_instance}"
162
+ end
163
+ EOM
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,35 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ class StubChain < Chain
5
+ def initialize(*args, &block)
6
+ record(:stub, *args, &block)
7
+ end
8
+
9
+ def invocation_order
10
+ @invocation_order ||= {
11
+ :stub => [nil],
12
+ :with => [:stub],
13
+ :and_return => [:with, :stub],
14
+ :and_raise => [:with, :stub],
15
+ :and_yield => [:with, :stub]
16
+ }
17
+ end
18
+
19
+ def expectation_fulfilled?
20
+ true
21
+ end
22
+
23
+ def expectation_fulfilled!
24
+ end
25
+
26
+ private
27
+ def verify_invocation_order(rspec_method_name, *args, &block)
28
+ unless invocation_order[rspec_method_name].include?(last_message)
29
+ raise(NoMethodError, "Undefined method #{rspec_method_name}")
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,34 @@
1
+ module RSpec
2
+ module Mocks
3
+ module AnyInstance
4
+ class StubChainChain < Chain
5
+ def initialize(*args, &block)
6
+ record(:stub_chain, *args, &block)
7
+ end
8
+
9
+ def invocation_order
10
+ @invocation_order ||= {
11
+ :stub_chain => [nil],
12
+ :and_return => [:stub_chain],
13
+ :and_raise => [:stub_chain],
14
+ :and_yield => [:stub_chain]
15
+ }
16
+ end
17
+
18
+ def expectation_fulfilled?
19
+ true
20
+ end
21
+
22
+ def expectation_fulfilled!
23
+ end
24
+
25
+ private
26
+ def verify_invocation_order(rspec_method_name, *args, &block)
27
+ unless invocation_order[rspec_method_name].include?(last_message)
28
+ raise(NoMethodError, "Undefined method #{rspec_method_name}")
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end