state_machine_rspec 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,327 @@
1
+ require 'spec_helper'
2
+ require 'integration/models/vehicle'
3
+
4
+ describe Vehicle do
5
+ let(:vehicle) { Vehicle.new }
6
+ subject { vehicle }
7
+
8
+ its(:passed_inspection?) { should be_false }
9
+
10
+ shared_examples 'crashable' do
11
+ describe 'crash' do
12
+ context 'having passed inspection' do
13
+ before { vehicle.stub(:passed_inspection).and_return(true) }
14
+ pending 'keeps running' do
15
+ initial_state = vehicle.state
16
+ vehicle.crash!
17
+
18
+ vehicle.state.should eq initial_state
19
+ end
20
+ end
21
+
22
+ context 'not having passed inspection' do
23
+ before { vehicle.stub(:passed_inspection).and_return(false) }
24
+ it 'stalls' do
25
+ vehicle.crash!
26
+ vehicle.state.should eq :stalled.to_s
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ shared_examples 'speedless' do
33
+ it 'does not respond to speed' do
34
+ expect { vehicle.speed }.to raise_error NoMethodError
35
+ end
36
+ end
37
+
38
+ describe '#initialize' do
39
+ its(:seatbelt_on) { should be_false }
40
+ its(:time_used) { should eq 0 }
41
+ its(:auto_shop_busy) { should be_true }
42
+ end
43
+
44
+ describe '#put_on_seatbelt' do
45
+ it 'sets seatbelt_on to true' do
46
+ vehicle.seatbelt_on = false
47
+ vehicle.put_on_seatbelt
48
+
49
+ vehicle.seatbelt_on.should be_true
50
+ end
51
+ end
52
+
53
+ describe 'state machine' do
54
+ it { should have_states :parked, :idling, :stalled, :first_gear,
55
+ :second_gear, :third_gear }
56
+ it { should reject_state :flying }
57
+
58
+ it { should handle_event :ignite, when: :parked }
59
+ it { should reject_events :park, :idle, :shift_up,
60
+ :shift_down, :crash, :repair,
61
+ when: :parked }
62
+
63
+ it { should handle_events :park, :shift_up, :crash, when: :idling }
64
+ it { should reject_events :ignite, :idle, :shift_down, :repair,
65
+ when: :idling }
66
+
67
+ it { should handle_events :ignite, :repair, when: :stalled }
68
+ it { should reject_events :park, :idle, :shift_up, :shift_down, :crash,
69
+ when: :stalled }
70
+
71
+ it { should handle_events :park, :idle, :shift_up, :crash,
72
+ when: :first_gear }
73
+ it { should reject_events :ignite, :shift_down, :repair,
74
+ when: :first_gear }
75
+
76
+ it { should handle_events :shift_up, :shift_down, :crash,
77
+ when: :second_gear }
78
+ it { should reject_events :park, :ignite, :idle, :repair,
79
+ when: :second_gear }
80
+
81
+ it { should handle_events :shift_down, :crash, when: :third_gear }
82
+ it { should reject_events :park, :ignite, :idle, :shift_up, :repair,
83
+ when: :third_gear }
84
+
85
+ it 'has an initial state of "parked"' do
86
+ vehicle.should be_parked
87
+ end
88
+
89
+ it 'has an initial alarm state of "active"' do
90
+ vehicle.alarm_active?.should be_true
91
+ end
92
+
93
+ describe 'around transitions' do
94
+ it 'updates the time used' do
95
+ vehicle.should_receive(:time_used=).with(0)
96
+ Timecop.freeze { vehicle.ignite! }
97
+ end
98
+ end
99
+
100
+ context 'when parked' do
101
+ before { vehicle.state = :parked.to_s }
102
+
103
+ its(:speed) { should be_zero }
104
+ it { should_not be_moving }
105
+
106
+ describe 'before transitions' do
107
+ it 'puts on a seatbelt' do
108
+ vehicle.should_receive :put_on_seatbelt
109
+ vehicle.ignite!
110
+ end
111
+ end
112
+
113
+ describe 'ignite' do
114
+ it 'should transition to idling' do
115
+ vehicle.ignite!
116
+ vehicle.should be_idling
117
+ end
118
+ end
119
+ end
120
+
121
+ context 'when transitioning to parked' do
122
+ before { vehicle.state = :idling.to_s }
123
+ it 'removes seatbelts' do
124
+ vehicle.should_receive(:seatbelt_on=).with(false)
125
+ vehicle.park!
126
+ end
127
+ end
128
+
129
+ context 'when idling' do
130
+ before { vehicle.state = :idling.to_s }
131
+
132
+ its(:speed) { should eq 10 }
133
+ it { should_not be_moving }
134
+
135
+ describe 'park' do
136
+ it 'should transition to a parked state' do
137
+ vehicle.park!
138
+ vehicle.should be_parked
139
+ end
140
+ end
141
+
142
+ describe 'shift up' do
143
+ it 'should shift into first gear' do
144
+ vehicle.shift_up!
145
+ vehicle.should be_first_gear
146
+ end
147
+ end
148
+
149
+ it_behaves_like 'crashable'
150
+ end
151
+
152
+ context 'when stalled' do
153
+ before { vehicle.state = :stalled.to_s }
154
+
155
+ it { should_not be_moving }
156
+ it_behaves_like 'speedless'
157
+
158
+ describe 'ignite' do
159
+ it 'remains stalled' do
160
+ vehicle.ignite!
161
+ vehicle.should be_stalled
162
+ end
163
+ end
164
+
165
+ describe 'repair' do
166
+ context 'the auto shop is busy' do
167
+ before { vehicle.stub(:auto_shop_busy).and_return(true) }
168
+ it 'remains stalled' do
169
+ vehicle.repair!
170
+ vehicle.should be_stalled
171
+ end
172
+ end
173
+
174
+ context 'the auto shop is not busy' do
175
+ before { vehicle.stub(:auto_shop_busy).and_return(false) }
176
+ it 'is parked' do
177
+ vehicle.repair!
178
+ vehicle.should be_parked
179
+ end
180
+ end
181
+ end
182
+ end
183
+
184
+ context 'when in first gear' do
185
+ before { vehicle.state = :first_gear.to_s }
186
+
187
+ its(:speed) { should eq 10 }
188
+ it { should be_moving }
189
+
190
+ describe 'park' do
191
+ it 'parks' do
192
+ vehicle.park!
193
+ vehicle.should be_parked
194
+ end
195
+ end
196
+
197
+ describe 'idle' do
198
+ it 'idles' do
199
+ vehicle.idle!
200
+ vehicle.should be_idling
201
+ end
202
+ end
203
+
204
+ describe 'shift up' do
205
+ it 'shift into second gear' do
206
+ vehicle.shift_up!
207
+ vehicle.should be_second_gear
208
+ end
209
+ end
210
+
211
+ it_behaves_like 'crashable'
212
+ end
213
+
214
+ context 'when in second gear' do
215
+ before { vehicle.state = :second_gear.to_s }
216
+
217
+ it { should be_moving }
218
+ it_behaves_like 'speedless'
219
+
220
+ describe 'shift up' do
221
+ it 'shifts into third gear' do
222
+ vehicle.shift_up!
223
+ vehicle.should be_third_gear
224
+ end
225
+ end
226
+
227
+ describe 'shift down' do
228
+ it 'shifts back into first gear' do
229
+ vehicle.shift_down!
230
+ vehicle.should be_first_gear
231
+ end
232
+ end
233
+
234
+ it_behaves_like 'crashable'
235
+ end
236
+
237
+ context 'when in third gear' do
238
+ before { vehicle.state = :third_gear.to_s }
239
+
240
+ it { should be_moving }
241
+ it_behaves_like 'speedless'
242
+
243
+ describe 'shift down' do
244
+ it 'shifts back into second gear' do
245
+ vehicle.shift_down!
246
+ vehicle.should be_second_gear
247
+ end
248
+ end
249
+
250
+ it_behaves_like 'crashable'
251
+ end
252
+
253
+ context 'on ignition' do
254
+ context 'when it fails' do
255
+ before { vehicle.stub(:ignite).and_return(false) }
256
+ pending 'logs the failure' do
257
+ vehicle.should_receive(:log_start_failure)
258
+ vehicle.ignite
259
+ end
260
+ end
261
+ end
262
+
263
+ context 'on a crash' do
264
+ before { vehicle.state = :third_gear.to_s }
265
+ it 'gets towed' do
266
+ vehicle.should_receive(:tow)
267
+ vehicle.crash!
268
+ end
269
+ end
270
+
271
+ context 'upon being repaired' do
272
+ before { vehicle.state = :stalled.to_s }
273
+ it 'gets fixed' do
274
+ vehicle.should_receive(:fix)
275
+ vehicle.repair!
276
+ end
277
+ end
278
+ end
279
+
280
+ describe 'alarm state machine' do
281
+ it { should have_state :active, on: :alarm_state, value: 1 }
282
+ it { should have_state :off, on: :alarm_state, value: 0 }
283
+ it { should reject_states :broken, :ringing, on: :alarm_state }
284
+
285
+ it { should handle_events :enable_alarm, :disable_alarm,
286
+ when: :active, state: :alarm_state }
287
+ it { should handle_events :enable_alarm, :disable_alarm,
288
+ when: :off, state: :alarm_state }
289
+
290
+ it 'has an initial state of activated' do
291
+ vehicle.alarm_active?.should be_true
292
+ end
293
+
294
+ context 'when active' do
295
+ describe 'enable' do
296
+ it 'becomes active' do
297
+ vehicle.enable_alarm!
298
+ vehicle.alarm_active?.should be_true
299
+ end
300
+ end
301
+
302
+ describe 'disable' do
303
+ it 'turns the alarm off' do
304
+ vehicle.disable_alarm!
305
+ vehicle.alarm_off?.should be_true
306
+ end
307
+ end
308
+ end
309
+
310
+ context 'when off' do
311
+ before { vehicle.alarm_state = 0 }
312
+ describe 'enable' do
313
+ it 'becomes active' do
314
+ vehicle.enable_alarm!
315
+ vehicle.alarm_active?.should be_true
316
+ end
317
+ end
318
+
319
+ describe 'disable' do
320
+ it 'turns the alarm off' do
321
+ vehicle.disable_alarm!
322
+ vehicle.alarm_off?.should be_true
323
+ end
324
+ end
325
+ end
326
+ end
327
+ end
@@ -0,0 +1,119 @@
1
+ require 'spec_helper'
2
+
3
+ class Vehicle
4
+ attr_accessor :seatbelt_on, :time_used, :auto_shop_busy
5
+
6
+ state_machine :state, :initial => :parked do
7
+ before_transition :parked => any - :parked, :do => :put_on_seatbelt
8
+
9
+ after_transition :on => :crash, :do => :tow
10
+ after_transition :on => :repair, :do => :fix
11
+ after_transition any => :parked do |vehicle, transition|
12
+ vehicle.seatbelt_on = false
13
+ end
14
+
15
+ after_failure :on => :ignite, :do => :log_start_failure
16
+
17
+ around_transition do |vehicle, transition, block|
18
+ start = Time.now
19
+ block.call
20
+ vehicle.time_used += Time.now - start
21
+ end
22
+
23
+ event :park do
24
+ transition [:idling, :first_gear] => :parked
25
+ end
26
+
27
+ event :ignite do
28
+ transition :stalled => same, :parked => :idling
29
+ end
30
+
31
+ event :idle do
32
+ transition :first_gear => :idling
33
+ end
34
+
35
+ event :shift_up do
36
+ transition :idling => :first_gear, :first_gear => :second_gear, :second_gear => :third_gear
37
+ end
38
+
39
+ event :shift_down do
40
+ transition :third_gear => :second_gear, :second_gear => :first_gear
41
+ end
42
+
43
+ event :crash do
44
+ transition all - [:parked, :stalled] => :stalled, :if => lambda {|vehicle| !vehicle.passed_inspection?}
45
+ end
46
+
47
+ event :repair do
48
+ # The first transition that matches the state and passes its conditions
49
+ # will be used
50
+ transition :stalled => :parked, :unless => :auto_shop_busy
51
+ transition :stalled => same
52
+ end
53
+
54
+ state :parked do
55
+ def speed
56
+ 0
57
+ end
58
+ end
59
+
60
+ state :idling, :first_gear do
61
+ def speed
62
+ 10
63
+ end
64
+ end
65
+
66
+ state all - [:parked, :stalled, :idling] do
67
+ def moving?
68
+ true
69
+ end
70
+ end
71
+
72
+ state :parked, :stalled, :idling do
73
+ def moving?
74
+ false
75
+ end
76
+ end
77
+ end
78
+
79
+ state_machine :alarm_state, :initial => :active, :namespace => 'alarm' do
80
+ event :enable do
81
+ transition all => :active
82
+ end
83
+
84
+ event :disable do
85
+ transition all => :off
86
+ end
87
+
88
+ state :active, :value => 1
89
+ state :off, :value => 0
90
+ end
91
+
92
+ def initialize
93
+ @seatbelt_on = false
94
+ @time_used = 0
95
+ @auto_shop_busy = true
96
+ super() # NOTE: This *must* be called, otherwise states won't get initialized
97
+ end
98
+
99
+ def put_on_seatbelt
100
+ @seatbelt_on = true
101
+ end
102
+
103
+ def passed_inspection?
104
+ false
105
+ end
106
+
107
+ def tow
108
+ # tow the vehicle
109
+ end
110
+
111
+ def fix
112
+ # get the vehicle fixed by a mechanic
113
+ end
114
+
115
+ def log_start_failure
116
+ # log a failed attempt to start the vehicle
117
+ end
118
+ end
119
+
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachineRspec::Matchers::HandleEventMatcher do
4
+ describe '#matches?' do
5
+ context 'when :when state is specified' do
6
+ context 'but the state doesn\'t exist' do
7
+ before do
8
+ matcher_class = Class.new do
9
+ state_machine :state, initial: :mathy
10
+ end
11
+ @matcher_subject = matcher_class.new
12
+ @matcher = described_class.new([when: :artsy])
13
+ end
14
+
15
+ it 'raises' do
16
+ expect { @matcher.matches? @matcher_subject }.
17
+ to raise_error StateMachineIntrospectorError
18
+ end
19
+ end
20
+
21
+ context 'and that state exists' do
22
+ before do
23
+ matcher_class = Class.new do
24
+ state_machine :state, initial: :mathy do
25
+ state :artsy
26
+ end
27
+ end
28
+ @matcher_subject = matcher_class.new
29
+ @matcher = described_class.new([when: :artsy])
30
+ end
31
+
32
+ it 'sets the state' do
33
+ @matcher.matches? @matcher_subject
34
+ @matcher_subject.state.should eq 'artsy'
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'when subject can perform events' do
40
+ before do
41
+ matcher_class = Class.new do
42
+ state_machine :state, initial: :mathy do
43
+ event(:mathematize) { transition any => same }
44
+ end
45
+ end
46
+ @matcher_subject = matcher_class.new
47
+ @matcher = described_class.new([:mathematize])
48
+ end
49
+
50
+ it 'does not set a failure message' do
51
+ @matcher.matches? @matcher_subject
52
+ @matcher.failure_message.should be_nil
53
+ end
54
+ it 'returns true' do
55
+ @matcher.matches?(@matcher_subject).should be_true
56
+ end
57
+ end
58
+
59
+ context 'when subject cannot perform events' do
60
+ before do
61
+ matcher_class = Class.new do
62
+ state_machine :state, initial: :mathy do
63
+ state :polynomial
64
+
65
+ event(:mathematize) { transition any => same }
66
+ event(:algebraify) { transition :polynomial => same }
67
+ event(:trigonomalize) { transition :trigonomalize => same }
68
+ end
69
+ end
70
+ @matcher_subject = matcher_class.new
71
+ end
72
+
73
+ context 'because it cannot perform the transition' do
74
+ before do
75
+ @matcher = described_class.new([:mathematize, :algebraify, :trigonomalize])
76
+ end
77
+
78
+ it 'sets a failure message' do
79
+ @matcher.matches? @matcher_subject
80
+ @matcher.failure_message.
81
+ should eq 'Expected to be able to handle events: algebraify, trigonomalize ' +
82
+ 'in state: mathy'
83
+ end
84
+ it 'returns false' do
85
+ @matcher.matches?(@matcher_subject).should be_false
86
+ end
87
+ end
88
+
89
+ context 'because no such events exist' do
90
+ before do
91
+ @matcher = described_class.new([:polynomialize, :eulerasterize])
92
+ end
93
+
94
+ it 'does not raise' do
95
+ expect { @matcher.matches?(@matcher_subject) }.to_not raise_error
96
+ end
97
+ it 'sets a failure message' do
98
+ @matcher.matches? @matcher_subject
99
+ @matcher.failure_message.
100
+ should eq 'state_machine: state does not ' +
101
+ 'define events: polynomialize, eulerasterize'
102
+ end
103
+ it 'returns false' do
104
+ @matcher.matches?(@matcher_subject).should be_false
105
+ end
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+
3
+ describe StateMachineRspec::Matchers::RejectEventMatcher do
4
+ describe '#matches?' do
5
+ context 'when :when state is specified' do
6
+ context 'but that state doesn\'t exist' do
7
+ before do
8
+ matcher_class = Class.new do
9
+ state_machine :state, initial: :sleazy
10
+ end
11
+ @matcher_subject = matcher_class.new
12
+ @matcher = described_class.new([when: :sneezy])
13
+ end
14
+
15
+ it 'raises' do
16
+ expect { @matcher.matches? @matcher_subject }.
17
+ to raise_error StateMachineIntrospectorError
18
+ end
19
+ end
20
+
21
+ context 'and that state exists' do
22
+ before do
23
+ matcher_class = Class.new do
24
+ state_machine :state, initial: :sleazy do
25
+ state :sneezy
26
+ end
27
+ end
28
+ @matcher_subject = matcher_class.new
29
+ @matcher = described_class.new([when: :sneezy])
30
+ end
31
+
32
+ it 'sets the state' do
33
+ @matcher.matches? @matcher_subject
34
+ @matcher_subject.state.should eq 'sneezy'
35
+ end
36
+ end
37
+ end
38
+
39
+ context 'when an expectation is made on an event that is undefined' do
40
+ before do
41
+ matcher_class = Class.new do
42
+ state_machine :state, initial: :snarky do
43
+ event(:primmadonnalize) { transition any => same }
44
+ end
45
+ end
46
+ @matcher_subject = matcher_class.new
47
+ @matcher = described_class.new([:primmadonnalize, :martinilunchitize])
48
+ end
49
+
50
+ it 'does not raise' do
51
+ expect { @matcher.matches?(@matcher_subject) }.to_not raise_error
52
+ end
53
+ it 'sets a failure message' do
54
+ @matcher.matches? @matcher_subject
55
+ @matcher.failure_message.
56
+ should eq 'state_machine: state does not ' +
57
+ 'define events: martinilunchitize'
58
+ end
59
+ it 'returns false' do
60
+ @matcher.matches?(@matcher_subject).should be_false
61
+ end
62
+ end
63
+
64
+ context 'when subject cannot perform any of the specified events' do
65
+ before do
66
+ matcher_class = Class.new do
67
+ state_machine :state, initial: :snarky do
68
+ state :haughty
69
+ event(:primmadonnalize) { transition :haughty => same }
70
+ end
71
+ end
72
+ @matcher_subject = matcher_class.new
73
+ @matcher = described_class.new([:primmadonnalize])
74
+ end
75
+
76
+ it 'does not set a failure message' do
77
+ @matcher.matches? @matcher_subject
78
+ @matcher.failure_message.should be_nil
79
+ end
80
+ it 'returns true' do
81
+ @matcher.matches?(@matcher_subject).should be_true
82
+ end
83
+ end
84
+
85
+ context 'when subject can perform any one of the specified events' do
86
+ before do
87
+ matcher_class = Class.new do
88
+ state_machine :state, initial: :snarky do
89
+ state :haughty
90
+ event(:primmadonnalize) { transition :haughty => same }
91
+ event(:defer_to_management) { transition any => same }
92
+ end
93
+ end
94
+ @matcher_subject = matcher_class.new
95
+ @matcher = described_class.new([:primmadonnalize, :defer_to_management])
96
+ end
97
+
98
+ it 'sets a failure message' do
99
+ @matcher.matches? @matcher_subject
100
+ @matcher.failure_message.
101
+ should eq 'Did not expect to be able to handle events: defer_to_management ' +
102
+ 'in state: snarky'
103
+ end
104
+ it 'returns false' do
105
+ @matcher.matches?(@matcher_subject).should be_false
106
+ end
107
+ end
108
+ end
109
+ end