bumbleworks 0.0.48 → 0.0.50
Sign up to get free protection for your applications and to get access to all the features.
- 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
|