rspec-mocks 2.8.0.rc1 → 2.8.0.rc2

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.
@@ -1,18 +1,33 @@
1
1
  require 'rspec/mocks/any_instance/chain'
2
- require 'rspec/mocks/any_instance/stub_chain'
3
- require 'rspec/mocks/any_instance/stub_chain_chain'
4
- require 'rspec/mocks/any_instance/expectation_chain'
5
2
  require 'rspec/mocks/any_instance/message_chains'
6
3
  require 'rspec/mocks/any_instance/recorder'
7
4
 
8
5
  module RSpec
9
6
  module Mocks
10
7
  module AnyInstance
8
+ # Used to set stubs and message expectations on any instance of a given
9
+ # class. Returns a [Recorder](Recorder), which records messages like
10
+ # `stub` and `should_receive` for later playback on instances of the
11
+ # class.
12
+ #
13
+ # @example
14
+ #
15
+ # Car.any_instance.should_receive(:go)
16
+ # race = Race.new
17
+ # race.cars << Car.new
18
+ # race.go # assuming this delegates to all of its cars
19
+ # # this example would pass
20
+ #
21
+ # Account.any_instance.stub(:balance) { Money.new(:USD, 25) }
22
+ # Account.new.balance # => Money.new(:USD, 25))
23
+ #
24
+ # @return [Recorder]
11
25
  def any_instance
12
26
  RSpec::Mocks::space.add(self)
13
27
  __recorder
14
28
  end
15
29
 
30
+ # @private
16
31
  def rspec_verify
17
32
  __recorder.verify
18
33
  super
@@ -21,6 +36,7 @@ module RSpec
21
36
  @__recorder = nil
22
37
  end
23
38
 
39
+ # @private
24
40
  def __recorder
25
41
  @__recorder ||= AnyInstance::Recorder.new(self)
26
42
  end
@@ -2,25 +2,47 @@ module RSpec
2
2
  module Mocks
3
3
  module AnyInstance
4
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|
5
+ class << self
6
+ private
7
+
8
+ # @macro [attach] record
9
+ # @method $1(*args, &block)
10
+ # Records the `$1` message for playback against an instance that
11
+ # invokes a method stubbed or mocked using `any_instance`.
12
+ #
13
+ # @see RSpec::Mocks::MessageExpectation#$1
14
+ #
15
+ def record(method_name)
11
16
  class_eval(<<-EOM, __FILE__, __LINE__)
12
17
  def #{method_name}(*args, &block)
13
18
  record(:#{method_name}, *args, &block)
14
19
  end
15
20
  EOM
21
+ end
16
22
  end
17
23
 
24
+ record :and_return
25
+ record :and_raise
26
+ record :and_throw
27
+ record :and_yield
28
+ record :with
29
+ record :once
30
+ record :twice
31
+ record :any_number_of_times
32
+ record :exactly
33
+ record :times
34
+ record :never
35
+ record :at_least
36
+ record :at_most
37
+
38
+ # @private
18
39
  def playback!(instance)
19
40
  messages.inject(instance) do |_instance, message|
20
41
  _instance.__send__(*message.first, &message.last)
21
42
  end
22
43
  end
23
44
 
45
+ # @private
24
46
  def constrained_to_any_of?(*constraints)
25
47
  constraints.any? do |constraint|
26
48
  messages.any? do |message|
@@ -29,7 +51,13 @@ module RSpec
29
51
  end
30
52
  end
31
53
 
54
+ # @private
55
+ def expectation_fulfilled!
56
+ @expectation_fulfilled = true
57
+ end
58
+
32
59
  private
60
+
33
61
  def messages
34
62
  @messages ||= []
35
63
  end
@@ -44,6 +72,87 @@ module RSpec
44
72
  self
45
73
  end
46
74
  end
75
+
76
+ # @private
77
+ class ExpectationChain < Chain
78
+
79
+ # @private
80
+ def initialize(*args, &block)
81
+ record(:should_receive, *args, &block)
82
+ @expectation_fulfilled = false
83
+ end
84
+
85
+ # @private
86
+ def expectation_fulfilled?
87
+ @expectation_fulfilled || constrained_to_any_of?(:never, :any_number_of_times)
88
+ end
89
+
90
+ private
91
+
92
+ def verify_invocation_order(rspec_method_name, *args, &block)
93
+ end
94
+
95
+ def invocation_order
96
+ @invocation_order ||= {
97
+ :should_receive => [nil],
98
+ :with => [:should_receive],
99
+ :and_return => [:with, :should_receive],
100
+ :and_raise => [:with, :should_receive]
101
+ }
102
+ end
103
+ end
104
+
105
+ # @private
106
+ class StubChain < Chain
107
+
108
+ # @private
109
+ def initialize(*args, &block)
110
+ record(:stub, *args, &block)
111
+ end
112
+
113
+ # @private
114
+ def expectation_fulfilled?
115
+ true
116
+ end
117
+
118
+ private
119
+
120
+ def invocation_order
121
+ @invocation_order ||= {
122
+ :stub => [nil],
123
+ :with => [:stub],
124
+ :and_return => [:with, :stub],
125
+ :and_raise => [:with, :stub],
126
+ :and_yield => [:with, :stub]
127
+ }
128
+ end
129
+
130
+ def verify_invocation_order(rspec_method_name, *args, &block)
131
+ unless invocation_order[rspec_method_name].include?(last_message)
132
+ raise(NoMethodError, "Undefined method #{rspec_method_name}")
133
+ end
134
+ end
135
+ end
136
+
137
+ # @private
138
+ class StubChainChain < StubChain
139
+
140
+ # @private
141
+ def initialize(*args, &block)
142
+ record(:stub_chain, *args, &block)
143
+ end
144
+
145
+ private
146
+
147
+ def invocation_order
148
+ @invocation_order ||= {
149
+ :stub_chain => [nil],
150
+ :and_return => [:stub_chain],
151
+ :and_raise => [:stub_chain],
152
+ :and_yield => [:stub_chain]
153
+ }
154
+ end
155
+ end
47
156
  end
48
157
  end
49
158
  end
@@ -1,48 +1,60 @@
1
1
  module RSpec
2
2
  module Mocks
3
3
  module AnyInstance
4
+ # @private
4
5
  class MessageChains < Hash
6
+ def initialize
7
+ super {|h,k| h[k] = []}
8
+ end
9
+
10
+ # @private
5
11
  def add(method_name, chain)
6
- (self[method_name] ||= []) << chain
12
+ self[method_name] << chain
13
+ chain
7
14
  end
8
-
15
+
16
+ # @private
9
17
  def remove_stub_chains_for!(method_name)
10
- chains = self[method_name]
11
- chains.reject! { |chain| chain.is_a?(StubChain) || chain.is_a?(StubChainChain) }
18
+ self[method_name].reject! {|chain| chain.is_a?(StubChain)}
12
19
  end
13
-
20
+
21
+ # @private
14
22
  def has_expectation?(method_name)
15
- !!self[method_name].find{|chain| chain.is_a?(ExpectationChain)}
23
+ self[method_name].find {|chain| chain.is_a?(ExpectationChain)}
16
24
  end
17
-
18
- def each_expectation_fulfilled?
19
- self.all? do |method_name, chains|
20
- chains.all? { |chain| chain.expectation_fulfilled? }
21
- end
25
+
26
+ # @private
27
+ def all_expectations_fulfilled?
28
+ all? {|method_name, chains| chains.all? {|chain| chain.expectation_fulfilled?}}
22
29
  end
23
30
 
31
+ # @private
24
32
  def unfulfilled_expectations
25
- self.map do |method_name, chains|
33
+ map do |method_name, chains|
26
34
  method_name.to_s if chains.last.is_a?(ExpectationChain) unless chains.last.expectation_fulfilled?
27
35
  end.compact
28
36
  end
29
37
 
38
+ # @private
30
39
  def received_expected_message!(method_name)
31
- self[method_name].each do |chain|
32
- chain.expectation_fulfilled!
33
- end
40
+ self[method_name].each {|chain| chain.expectation_fulfilled!}
34
41
  end
35
-
42
+
43
+ # @private
36
44
  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)
45
+ raise_if_second_instance_to_receive_message(instance)
46
+ self[method_name].each {|chain| chain.playback!(instance)}
47
+ end
48
+
49
+ private
50
+
51
+ def raise_if_second_instance_to_receive_message(instance)
52
+ @instance_with_expectation ||= instance if instance.is_a?(ExpectationChain)
53
+ if instance.is_a?(ExpectationChain) && !@instance_with_expectation.equal?(instance)
54
+ raise RSpec::Mocks::MockExpectationError, "Exactly one instance should have received the following message(s) but didn't: #{unfulfilled_expectations.sort.join(', ')}"
43
55
  end
44
56
  end
45
57
  end
46
58
  end
47
59
  end
48
- end
60
+ end
@@ -1,8 +1,18 @@
1
1
  module RSpec
2
2
  module Mocks
3
3
  module AnyInstance
4
+ # Given a class `TheClass`, `TheClass.any_instance` returns a `Recorder`,
5
+ # which records stubs and message expectations for later playback on
6
+ # instances of `TheClass`.
7
+ #
8
+ # Further constraints are stored in instances of [Chain](Chain).
9
+ #
10
+ # @see AnyInstance
11
+ # @see Chain
4
12
  class Recorder
13
+ # @private
5
14
  attr_reader :message_chains
15
+
6
16
  def initialize(klass)
7
17
  @message_chains = MessageChains.new
8
18
  @observed_methods = []
@@ -10,7 +20,49 @@ module RSpec
10
20
  @klass = klass
11
21
  @expectation_set = false
12
22
  end
13
-
23
+
24
+ # Initializes the recording a stub to be played back against any
25
+ # instance of this object that invokes the submitted method.
26
+ #
27
+ # @see Methods#stub
28
+ def stub(method_name_or_method_map, &block)
29
+ if method_name_or_method_map.is_a?(Hash)
30
+ method_name_or_method_map.each do |method_name, return_value|
31
+ stub(method_name).and_return(return_value)
32
+ end
33
+ else
34
+ observe!(method_name_or_method_map)
35
+ message_chains.add(method_name_or_method_map, StubChain.new(method_name_or_method_map, &block))
36
+ end
37
+ end
38
+
39
+ # Initializes the recording a stub chain to be played back against any
40
+ # instance of this object that invokes the method matching the first
41
+ # argument.
42
+ #
43
+ # @see Methods#stub_chain
44
+ def stub_chain(*method_names_and_optional_return_values, &block)
45
+ normalize_chain(*method_names_and_optional_return_values) do |method_name, args|
46
+ observe!(method_name)
47
+ message_chains.add(method_name, StubChainChain.new(*args, &block))
48
+ end
49
+ end
50
+
51
+ # Initializes the recording a message expectation to be played back
52
+ # against any instance of this object that invokes the submitted
53
+ # method.
54
+ #
55
+ # @see Methods#should_receive
56
+ def should_receive(method_name, &block)
57
+ @expectation_set = true
58
+ observe!(method_name)
59
+ message_chains.add(method_name, ExpectationChain.new(method_name, &block))
60
+ end
61
+
62
+ # Removes any previously recorded stubs, stub_chains or message
63
+ # expectations that use `method_name`.
64
+ #
65
+ # @see Methods#unstub
14
66
  def unstub(method_name)
15
67
  unless @observed_methods.include?(method_name.to_sym)
16
68
  raise RSpec::Mocks::MockExpectationError, "The method `#{method_name}` was not stubbed or was already unstubbed"
@@ -18,52 +70,28 @@ module RSpec
18
70
  message_chains.remove_stub_chains_for!(method_name)
19
71
  stop_observing!(method_name) unless message_chains.has_expectation?(method_name)
20
72
  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
73
+
74
+ # @api private
75
+ #
76
+ # Used internally to verify that message expectations have been
77
+ # fulfilled.
78
+ def verify
79
+ if @expectation_set && !message_chains.all_expectations_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(', ')}"
36
81
  end
37
82
  end
38
83
 
84
+ # @private
39
85
  def stub!(*)
40
86
  raise "stub! is not supported on any_instance. Use stub instead."
41
87
  end
42
88
 
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
-
89
+ # @private
61
90
  def stop_all_observation!
62
- @observed_methods.each do |method_name|
63
- restore_method!(method_name)
64
- end
91
+ @observed_methods.each {|method_name| restore_method!(method_name)}
65
92
  end
66
93
 
94
+ # @private
67
95
  def playback!(instance, method_name)
68
96
  RSpec::Mocks::space.add(instance)
69
97
  message_chains.playback!(instance, method_name)
@@ -71,27 +99,24 @@ module RSpec
71
99
  received_expected_message!(method_name) if message_chains.has_expectation?(method_name)
72
100
  end
73
101
 
102
+ # @private
74
103
  def instance_that_received(method_name)
75
104
  @played_methods[method_name]
76
105
  end
77
106
 
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
107
  private
85
- def period_separated_method_chain?(method_name)
86
- method_name.is_a?(String) && method_name.include?('.')
108
+
109
+ def normalize_chain(*args)
110
+ args.shift.to_s.split('.').map {|s| s.to_sym}.reverse.each {|a| args.unshift a}
111
+ yield args.first, args
87
112
  end
88
-
113
+
89
114
  def received_expected_message!(method_name)
90
115
  message_chains.received_expected_message!(method_name)
91
116
  restore_method!(method_name)
92
117
  mark_invoked!(method_name)
93
118
  end
94
-
119
+
95
120
  def restore_method!(method_name)
96
121
  if public_protected_or_private_method_defined?(build_alias_method_name(method_name))
97
122
  restore_original_method!(method_name)
@@ -125,11 +150,11 @@ module RSpec
125
150
  alias_method alias_method_name, method_name
126
151
  end if public_protected_or_private_method_defined?(method_name)
127
152
  end
128
-
153
+
129
154
  def public_protected_or_private_method_defined?(method_name)
130
155
  @klass.method_defined?(method_name) || @klass.private_method_defined?(method_name)
131
156
  end
132
-
157
+
133
158
  def stop_observing!(method_name)
134
159
  restore_method!(method_name)
135
160
  @observed_methods.delete(method_name)