rspec-mocks 2.8.0.rc1 → 2.8.0.rc2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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)