bumbleworks 0.0.48 → 0.0.50
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.
- data/.gitignore +4 -1
- data/lib/bumbleworks.rb +9 -8
- data/lib/bumbleworks/configuration.rb +8 -5
- data/lib/bumbleworks/entity.rb +88 -0
- data/lib/bumbleworks/participant.rb +6 -7
- data/lib/bumbleworks/participant/base.rb +11 -0
- data/lib/bumbleworks/participant/entity_interactor.rb +29 -0
- data/lib/bumbleworks/participant/error_handler.rb +14 -0
- data/lib/bumbleworks/participant/local_participant.rb +11 -0
- data/lib/bumbleworks/participant/storage_participant.rb +11 -0
- data/lib/bumbleworks/process.rb +64 -0
- data/lib/bumbleworks/ruote.rb +12 -4
- data/lib/bumbleworks/support.rb +3 -5
- data/lib/bumbleworks/task.rb +6 -4
- data/lib/bumbleworks/{tasks → task}/base.rb +1 -1
- data/lib/bumbleworks/task/finder.rb +39 -3
- data/lib/bumbleworks/version.rb +1 -1
- data/spec/fixtures/entities/furby.rb +30 -0
- data/spec/integration/entity_spec.rb +49 -0
- data/spec/integration/history_storage_spec.rb +4 -4
- data/spec/integration/sample_application_spec.rb +8 -2
- data/spec/lib/bumbleworks/entity_spec.rb +208 -0
- data/spec/lib/bumbleworks/{participant_spec.rb → participant/base_spec.rb} +3 -3
- data/spec/lib/bumbleworks/participant/entity_interactor_spec.rb +91 -0
- data/spec/lib/bumbleworks/{error_handler_participant_spec.rb → participant/error_handler_spec.rb} +1 -1
- data/spec/lib/bumbleworks/{local_participant_spec.rb → participant/local_participant_spec.rb} +1 -1
- data/spec/lib/bumbleworks/process_spec.rb +147 -0
- data/spec/lib/bumbleworks/ruote/exp/broadcast_event_expression_spec.rb +1 -1
- data/spec/lib/bumbleworks/ruote/exp/wait_for_event_expression_spec.rb +3 -3
- data/spec/lib/bumbleworks/ruote_spec.rb +28 -21
- data/spec/lib/bumbleworks/task/finder_spec.rb +9 -0
- data/spec/lib/bumbleworks/task_spec.rb +98 -3
- data/spec/lib/bumbleworks_spec.rb +14 -7
- data/spec/spec_helper.rb +0 -1
- data/spec/support/process_testing_helpers.rb +9 -0
- metadata +34 -13
- data/lib/bumbleworks/error_handler_participant.rb +0 -12
- data/lib/bumbleworks/local_participant.rb +0 -9
- data/lib/bumbleworks/storage_participant.rb +0 -9
data/lib/bumbleworks/version.rb
CHANGED
@@ -0,0 +1,30 @@
|
|
1
|
+
class Furby
|
2
|
+
include Bumbleworks::Entity
|
3
|
+
|
4
|
+
process :make_honey
|
5
|
+
|
6
|
+
attr_reader :identifier
|
7
|
+
attr_accessor :make_honey_process_identifier
|
8
|
+
|
9
|
+
def initialize(identifier)
|
10
|
+
@identifier = identifier
|
11
|
+
end
|
12
|
+
|
13
|
+
def ==(other)
|
14
|
+
other.identifier == identifier
|
15
|
+
end
|
16
|
+
|
17
|
+
def update(attributes)
|
18
|
+
attributes.each { |k, v| self.send(:"#{k}=", v) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def cook_it_up(*foods)
|
22
|
+
foods.join(' and ')
|
23
|
+
end
|
24
|
+
|
25
|
+
class << self
|
26
|
+
def first_by_identifier(identifier)
|
27
|
+
new(identifier)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require File.expand_path(File.join(fixtures_path, 'entities', 'furby'))
|
2
|
+
|
3
|
+
describe 'Entity Module' do
|
4
|
+
let(:app_root) {
|
5
|
+
File.expand_path(File.join(fixtures_path, 'apps', 'with_default_directories'))
|
6
|
+
}
|
7
|
+
|
8
|
+
before :each do
|
9
|
+
Bumbleworks.reset!
|
10
|
+
load File.join(app_root, 'full_initializer.rb')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'launching a process' do
|
14
|
+
it 'assigns entity to process and subsequent tasks' do
|
15
|
+
furby = Furby.new('12345')
|
16
|
+
furby.launch_process(:make_honey)
|
17
|
+
Bumbleworks.dashboard.wait_for(:dave)
|
18
|
+
task = Bumbleworks::Task.for_role('dave').first
|
19
|
+
task.entity.should == furby
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'links processes with identifiers' do
|
23
|
+
furby = Furby.new('12345')
|
24
|
+
process = furby.launch_process(:make_honey)
|
25
|
+
furby.processes_by_name.should == {
|
26
|
+
:make_honey => process
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'persists process identifier' do
|
31
|
+
furby = Furby.new('12345')
|
32
|
+
process = furby.launch_process(:make_honey)
|
33
|
+
furby.make_honey_process_identifier.should == process.wfid
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe 'using the entity interactor participant' do
|
38
|
+
it 'calls a method on the entity' do
|
39
|
+
Bumbleworks.define_process 'here_we_go' do
|
40
|
+
tell_entity :to => 'cook_it_up', :with => [2, 'orange'], :and_save_as => 'yum'
|
41
|
+
critic :task => 'checkit_that_foods'
|
42
|
+
end
|
43
|
+
furby = Furby.new('12345')
|
44
|
+
process = Bumbleworks.launch!('here_we_go', :entity => furby)
|
45
|
+
Bumbleworks.dashboard.wait_for(:critic)
|
46
|
+
process.tasks.first['yum'].should == "2 and orange"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -25,10 +25,10 @@ describe 'History storage' do
|
|
25
25
|
|
26
26
|
it 'keeps history of messages' do
|
27
27
|
Bumbleworks::Ruote.storage.get_many('history').should be_empty
|
28
|
-
|
28
|
+
process = Bumbleworks.launch!('make_honey')
|
29
29
|
Bumbleworks.dashboard.wait_for(:dave)
|
30
30
|
Bumbleworks::Ruote.storage.get_many('history').should_not be_empty
|
31
|
-
Bumbleworks.dashboard.history.wfids.should include(wfid)
|
31
|
+
Bumbleworks.dashboard.history.wfids.should include(process.wfid)
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
@@ -43,10 +43,10 @@ describe 'History storage' do
|
|
43
43
|
|
44
44
|
it 'keeps history of messages' do
|
45
45
|
Bumbleworks.dashboard.history.all.should be_empty
|
46
|
-
|
46
|
+
process = Bumbleworks.launch!('make_honey')
|
47
47
|
Bumbleworks.dashboard.wait_for(:dave)
|
48
48
|
Bumbleworks.dashboard.history.all.should_not be_empty
|
49
|
-
Bumbleworks.dashboard.history.wfids.should include(wfid)
|
49
|
+
Bumbleworks.dashboard.history.wfids.should include(process.wfid)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -10,8 +10,14 @@ describe 'Bumbleworks Sample Application' do
|
|
10
10
|
|
11
11
|
describe 'initializer' do
|
12
12
|
it 'registers participants' do
|
13
|
-
Bumbleworks.dashboard.participant_list.should have(
|
14
|
-
Bumbleworks.dashboard.participant_list.map(&:classname).should
|
13
|
+
Bumbleworks.dashboard.participant_list.should have(5).items
|
14
|
+
Bumbleworks.dashboard.participant_list.map(&:classname).should == [
|
15
|
+
'Bumbleworks::Participant::ErrorHandler',
|
16
|
+
'Bumbleworks::Participant::EntityInteractor',
|
17
|
+
'HoneyParticipant',
|
18
|
+
'MolassesParticipant',
|
19
|
+
'Bumbleworks::Participant::StorageParticipant'
|
20
|
+
]
|
15
21
|
end
|
16
22
|
|
17
23
|
it 'loads process definitions' do
|
@@ -0,0 +1,208 @@
|
|
1
|
+
describe Bumbleworks::Entity do
|
2
|
+
let(:entity_class) { Class.new { include Bumbleworks::Entity } }
|
3
|
+
|
4
|
+
describe '#processes_by_name' do
|
5
|
+
it 'returns hash of process names and process instances' do
|
6
|
+
[:zoom, :foof, :nook].each do |pname|
|
7
|
+
entity_class.send(:attr_accessor, :"#{pname}_pid")
|
8
|
+
entity_class.process pname, :column => :"#{pname}_pid"
|
9
|
+
end
|
10
|
+
|
11
|
+
entity = entity_class.new
|
12
|
+
entity.foof_pid = '1234'
|
13
|
+
entity.nook_pid = 'pickles'
|
14
|
+
entity.processes_by_name.should == {
|
15
|
+
:zoom => nil,
|
16
|
+
:foof => Bumbleworks::Process.new('1234'),
|
17
|
+
:nook => Bumbleworks::Process.new('pickles')
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
it 'returns empty hash if no processes' do
|
22
|
+
entity_class.new.processes_by_name.should == {}
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#processes' do
|
27
|
+
it 'returns array of process instances for all running processes' do
|
28
|
+
[:zoom, :foof, :nook].each do |pname|
|
29
|
+
entity_class.send(:attr_accessor, :"#{pname}_pid")
|
30
|
+
entity_class.process pname, :column => :"#{pname}_pid"
|
31
|
+
end
|
32
|
+
entity = entity_class.new
|
33
|
+
entity.foof_pid = '1234'
|
34
|
+
entity.nook_pid = 'pickles'
|
35
|
+
entity.processes.should =~ [
|
36
|
+
Bumbleworks::Process.new('1234'),
|
37
|
+
Bumbleworks::Process.new('pickles')
|
38
|
+
]
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'returns empty array if no processes' do
|
42
|
+
entity_class.new.processes.should == []
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe '#cancel_all_processes!' do
|
47
|
+
it 'cancels all processes with registered identifiers' do
|
48
|
+
entity = entity_class.new
|
49
|
+
bp1 = Bumbleworks::Process.new('1234')
|
50
|
+
bp2 = Bumbleworks::Process.new('pickles')
|
51
|
+
entity.stub(:processes).and_return([bp1, bp2])
|
52
|
+
bp1.should_receive(:cancel!)
|
53
|
+
bp2.should_receive(:cancel!)
|
54
|
+
entity.cancel_all_processes!
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#process_fields' do
|
59
|
+
it 'returns hash with entity' do
|
60
|
+
entity = entity_class.new
|
61
|
+
entity.process_fields.should == { :entity => entity }
|
62
|
+
end
|
63
|
+
|
64
|
+
it 'accepts but ignores process name argument' do
|
65
|
+
entity = entity_class.new
|
66
|
+
entity.process_fields('ignore_me').should == { :entity => entity }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe '#process_variables' do
|
71
|
+
it 'returns empty hash' do
|
72
|
+
entity_class.new.process_variables.should == {}
|
73
|
+
end
|
74
|
+
|
75
|
+
it 'accepts but ignores process name argument' do
|
76
|
+
entity_class.new.process_variables('ignore me').should == {}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
describe '#persist_process_identifier' do
|
81
|
+
it 'calls #update if method exists' do
|
82
|
+
entity = entity_class.new
|
83
|
+
entity.should_receive(:update).with(:a_column => :a_value)
|
84
|
+
entity.persist_process_identifier(:a_column, :a_value)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'raises exception if #update method does not exist' do
|
88
|
+
entity = entity_class.new
|
89
|
+
entity.stub(:respond_to?).with(:update).and_return(false)
|
90
|
+
entity.should_receive(:update).never
|
91
|
+
expect {
|
92
|
+
entity.persist_process_identifier(:a_column, :a_value)
|
93
|
+
}.to raise_error("Entity must define #persist_process_identifier method if missing #update method.")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
describe '#launch_process' do
|
98
|
+
it 'launches process and return process if identifier not set' do
|
99
|
+
bp = Bumbleworks::Process.new('12345')
|
100
|
+
entity_class.process :noodles, :column => :noodles_pid
|
101
|
+
entity = entity_class.new
|
102
|
+
entity.stub(:noodles_pid)
|
103
|
+
entity.stub(:process_fields).with(:noodles).and_return('the_fields')
|
104
|
+
entity.stub(:process_variables).with(:noodles).and_return('the_variables')
|
105
|
+
Bumbleworks.stub(:launch!).with('noodles', 'the_fields', 'the_variables').and_return(bp)
|
106
|
+
entity.should_receive(:persist_process_identifier).with(:noodles_pid, '12345')
|
107
|
+
entity.launch_process('noodles').should == bp
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'does nothing but returns existing process if identifier column already set' do
|
111
|
+
bp = Bumbleworks::Process.new('already set')
|
112
|
+
entity_class.process :noodles, :column => :noodles_pid
|
113
|
+
entity = entity_class.new
|
114
|
+
entity.stub(:noodles_pid => 'already set')
|
115
|
+
Bumbleworks.should_receive(:launch!).never
|
116
|
+
entity.launch_process('noodles').should == bp
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'launches new process anyway if identifier column already set but force is true' do
|
120
|
+
bp = Bumbleworks::Process.new('12345')
|
121
|
+
entity_class.process :noodles, :column => :noodles_pid
|
122
|
+
entity = entity_class.new
|
123
|
+
entity.stub(:noodles_pid => 'already set')
|
124
|
+
Bumbleworks.stub(:launch! => bp)
|
125
|
+
entity.should_receive(:persist_process_identifier).with(:noodles_pid, '12345')
|
126
|
+
entity.launch_process('noodles', :force => true).should == bp
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
describe '#subscribed_events' do
|
131
|
+
it 'returns aggregate of subscribed events from all processes' do
|
132
|
+
bp1 = double('bp1', :subscribed_events => ['chewing', 'popping', 'yakking'])
|
133
|
+
bp2 = double('bp2', :subscribed_events => ['moon', 'yakking'])
|
134
|
+
entity = entity_class.new
|
135
|
+
entity.stub(:processes).and_return({
|
136
|
+
:zip => bp1,
|
137
|
+
:yip => bp2
|
138
|
+
})
|
139
|
+
entity.subscribed_events.should =~ ['chewing', 'moon', 'popping', 'yakking']
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
describe '#is_waiting_for?' do
|
144
|
+
it 'returns true if any processes are waiting for event' do
|
145
|
+
bp1 = double('bp1', :subscribed_events => ['chewing', 'popping', 'yakking'])
|
146
|
+
bp2 = double('bp2', :subscribed_events => ['moon', 'yakking'])
|
147
|
+
entity = entity_class.new
|
148
|
+
entity.stub(:processes).and_return({
|
149
|
+
:zip => bp1,
|
150
|
+
:yip => bp2
|
151
|
+
})
|
152
|
+
entity.is_waiting_for?('chewing').should be_true
|
153
|
+
entity.is_waiting_for?('yakking').should be_true
|
154
|
+
entity.is_waiting_for?(:moon).should be_true
|
155
|
+
entity.is_waiting_for?('fruiting').should be_false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
describe '#tasks' do
|
160
|
+
it 'returns task query for all entity tasks by default' do
|
161
|
+
entity = entity_class.new
|
162
|
+
Bumbleworks::Task.stub(:for_entity).with(entity).and_return(:full_task_query)
|
163
|
+
entity.tasks.should == :full_task_query
|
164
|
+
end
|
165
|
+
|
166
|
+
it 'filters task query by nickname if provided' do
|
167
|
+
entity = entity_class.new
|
168
|
+
task_finder = double('task_finder')
|
169
|
+
task_finder.stub(:by_nickname).with(:smooface).and_return(:partial_task_query)
|
170
|
+
Bumbleworks::Task.stub(:for_entity).with(entity).and_return(task_finder)
|
171
|
+
entity.tasks(:smooface).should == :partial_task_query
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
describe '.process' do
|
176
|
+
it 'registers a new process' do
|
177
|
+
entity_class.stub(:process_identifier_column).with(:whatever).and_return('loob')
|
178
|
+
entity_class.process :whatever
|
179
|
+
entity_class.processes.should == {
|
180
|
+
:whatever => {
|
181
|
+
:column => 'loob'
|
182
|
+
}
|
183
|
+
}
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe '.process_identifier_column' do
|
188
|
+
it 'adds _process_identifier to end of given process name' do
|
189
|
+
entity_class.process_identifier_column('zoof').should == :zoof_process_identifier
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'ensures no duplication of _process' do
|
193
|
+
entity_class.process_identifier_column('zoof_process').should == :zoof_process_identifier
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'removes entity_type from beginning of identifier' do
|
197
|
+
entity_class.stub(:entity_type).and_return('zoof')
|
198
|
+
entity_class.process_identifier_column('zoof_process').should == :process_identifier
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
describe '.entity_type' do
|
203
|
+
it 'returns underscored version of class name' do
|
204
|
+
entity_class.stub(:name).and_return('MyClass')
|
205
|
+
entity_class.entity_type.should == 'my_class'
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -1,6 +1,6 @@
|
|
1
|
-
describe Bumbleworks::Participant do
|
2
|
-
it 'includes Bumbleworks::LocalParticipant' do
|
3
|
-
described_class.included_modules.should include(Bumbleworks::LocalParticipant)
|
1
|
+
describe Bumbleworks::Participant::Base do
|
2
|
+
it 'includes Bumbleworks::Participant::LocalParticipant' do
|
3
|
+
described_class.included_modules.should include(Bumbleworks::Participant::LocalParticipant)
|
4
4
|
end
|
5
5
|
|
6
6
|
it 'defines #on_cancel' do
|
@@ -0,0 +1,91 @@
|
|
1
|
+
describe Bumbleworks::Participant::EntityInteractor do
|
2
|
+
let(:entity) { double('entity', :cheek_depth => 'crazy deep') }
|
3
|
+
let(:workitem) { Ruote::Workitem.new('fields' => { 'params' => { 'method' => 'cheek_depth' }}) }
|
4
|
+
subject { part = described_class.new; part.stub(:entity => entity, :reply => nil); part }
|
5
|
+
|
6
|
+
describe '#on_workitem' do
|
7
|
+
it 'calls method then replies' do
|
8
|
+
subject.should_receive(:call_method).ordered
|
9
|
+
subject.should_receive(:reply).ordered
|
10
|
+
subject._on_workitem(workitem)
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'saves the requested attribute to a workitem field' do
|
14
|
+
workitem.fields['params']['and_save_as'] = 'entity_cheek_depth'
|
15
|
+
subject._on_workitem(workitem)
|
16
|
+
workitem.fields['entity_cheek_depth'].should == 'crazy deep'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'overwrites an existing workitem field value' do
|
20
|
+
workitem.fields['entity_cheek_depth'] = '14'
|
21
|
+
workitem.fields['params']['and_save_as'] = 'entity_cheek_depth'
|
22
|
+
subject._on_workitem(workitem)
|
23
|
+
workitem.fields['entity_cheek_depth'].should == 'crazy deep'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'calls the method even if no save_as to store the result' do
|
27
|
+
subject.entity.should_receive(:cheek_depth)
|
28
|
+
subject._on_workitem(workitem)
|
29
|
+
workitem.fields['entity_cheek_depth'].should be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'passes arguments to method' do
|
33
|
+
workitem.fields['params']['arguments'] = [1, 3, ['apple', 'fish']]
|
34
|
+
workitem.fields['params']['and_save_as'] = 'entity_cheek_depth'
|
35
|
+
subject.entity.should_receive(:cheek_depth).with(1, 3, ['apple', 'fish']).and_return('what')
|
36
|
+
subject._on_workitem(workitem)
|
37
|
+
workitem.fields['entity_cheek_depth'].should == 'what'
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'can accept "with" for arguments' do
|
41
|
+
workitem.fields['params']['with'] = { :regular => 'joes' }
|
42
|
+
subject.entity.should_receive(:cheek_depth).with({ :regular => 'joes' })
|
43
|
+
subject._on_workitem(workitem)
|
44
|
+
end
|
45
|
+
|
46
|
+
it 'can use "for" param for method' do
|
47
|
+
workitem.fields['params'] = { 'for' => 'grass_seed', 'and_save_as' => 'how_tasty' }
|
48
|
+
entity.should_receive(:grass_seed).and_return('tasty-o')
|
49
|
+
subject._on_workitem(workitem)
|
50
|
+
workitem.fields['how_tasty'].should == 'tasty-o'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'can use "to" param for method' do
|
54
|
+
workitem.fields['params'] = { 'to' => 'chew_things' }
|
55
|
+
entity.should_receive(:chew_things).and_return(nil)
|
56
|
+
subject._on_workitem(workitem)
|
57
|
+
workitem.fields['who_cares'].should be_nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'defaults to "method" when multiple options exist' do
|
61
|
+
workitem.fields['params'] = { 'method' => 'really_do_this', 'to' => 'not_this', 'for' => 'definitely_not_this' }
|
62
|
+
entity.should_receive(:really_do_this)
|
63
|
+
subject._on_workitem(workitem)
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'defaults to "to" when both "to" and "for"' do
|
67
|
+
workitem.fields['params'] = { 'to' => 'please_call_me', 'for' => 'call_me_maybe' }
|
68
|
+
entity.should_receive(:please_call_me)
|
69
|
+
subject._on_workitem(workitem)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#call_method' do
|
74
|
+
it 'calls the requested method on the entity' do
|
75
|
+
subject.call_method('cheek_depth').should == 'crazy deep'
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'saves the result if given a target' do
|
79
|
+
subject.workitem = workitem
|
80
|
+
subject.call_method('cheek_depth', :save_as => 'entity_cheek_depth')
|
81
|
+
workitem.fields['entity_cheek_depth'].should == 'crazy deep'
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'raises an exception if no method on entity' do
|
85
|
+
subject.stub(:entity).and_return('just an unassuming little string!')
|
86
|
+
expect {
|
87
|
+
subject.call_method('eat_television')
|
88
|
+
}.to raise_error(NoMethodError)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|