Skip to content

Commit 2bc9537

Browse files
committed
Add around hooks (#32)
1 parent e02b0b4 commit 2bc9537

File tree

11 files changed

+508
-143
lines changed

11 files changed

+508
-143
lines changed

features/cucumber-tck

Submodule cucumber-tck updated from 7b5b642 to 80eaf4d

features/step_definitions/cucumber_js_mappings.rb

+13-1
Original file line numberDiff line numberDiff line change
@@ -138,12 +138,24 @@ def write_world_function
138138
def write_passing_hook hook_type
139139
provide_cycle_logging_facilities
140140
define_hook = hook_type.capitalize
141-
append_support_code <<-EOF
141+
if hook_type == "around"
142+
append_support_code <<-EOF
143+
this.#{define_hook}(function(runScenario) {
144+
this.logCycleEvent('#{hook_type}-pre');
145+
runScenario(function(callback) {
146+
this.logCycleEvent('#{hook_type}-post');
147+
callback();
148+
});
149+
});
150+
EOF
151+
else
152+
append_support_code <<-EOF
142153
this.#{define_hook}(function(callback) {
143154
this.logCycleEvent('#{hook_type}');
144155
callback();
145156
});
146157
EOF
158+
end
147159
end
148160

149161
def write_scenario options = {}

features/step_definitions/cucumber_steps.js

+23-2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ var cucumberSteps = function() {
2727
callback();
2828
});
2929

30+
Given(/^a passing around hook$/, function(callback) {
31+
this.stepDefinitions += "this.Around(function(runScenario) {\
32+
world.logCycleEvent('around-pre');\
33+
runScenario(function(callback) {\
34+
world.logCycleEvent('around-post');\
35+
callback();\
36+
});\
37+
});\n";
38+
callback();
39+
});
40+
3041
Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) {
3142
this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\
3243
world.touchStep(\"" + stepName + "\");\
@@ -240,9 +251,19 @@ callback();\
240251

241252
Then(/^the (before|after) hook is fired (?:before|after) the scenario$/, function(hookType, callback) {
242253
if (hookType == 'before')
243-
this.assertCycleSequence(hookType, 'step');
254+
this.assertCycleSequence(hookType, 'step 1');
244255
else
245-
this.assertCycleSequence('step', hookType);
256+
this.assertCycleSequence('step 1', hookType);
257+
callback();
258+
});
259+
260+
Then(/^the around hook fires around the scenario$/, function(callback) {
261+
this.assertCycleSequence('around-pre', 'step 1', 'around-post');
262+
callback();
263+
});
264+
265+
Then(/^the around hook is fired around the other hooks$/, function(callback) {
266+
this.assertCycleSequence('around-pre', 'before', 'step 1', 'after', 'around-post');
246267
callback();
247268
});
248269

features/step_definitions/cucumber_world.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ proto.runFeatureWithSupportCodeSource = function runFeatureWithSupportCodeSource
4949
proto.runAScenario = function runAScenario(callback) {
5050
this.addScenario("", "Given a step");
5151
this.stepDefinitions += "Given(/^a step$/, function(callback) {\
52-
world.logCycleEvent('step');\
52+
world.logCycleEvent('step 1');\
5353
callback();\
5454
});";
5555
this.runFeature({}, callback);

lib/cucumber/runtime/ast_tree_walker.js

+5-16
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
4343
supportCodeLibrary.instantiateNewWorld(function(world) {
4444
self.setWorld(world);
4545
self.witnessNewScenario();
46-
var payload = { scenario: scenario };
47-
var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload);
48-
var hookedUpScenarioVisit = self.hookUpFunction(
49-
function(callback) { scenario.acceptVisitor(self, callback); }
46+
var payload = { scenario: scenario };
47+
var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload);
48+
var hookedUpScenarioVisit = supportCodeLibrary.hookUpFunctionWithWorld(
49+
function(callback) { scenario.acceptVisitor(self, callback); },
50+
world
5051
);
5152
self.broadcastEventAroundUserFunction(
5253
event,
@@ -111,18 +112,6 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
111112
);
112113
},
113114

114-
hookUpFunction: function hookUpFunction(hookedUpFunction) {
115-
return function(callback) {
116-
supportCodeLibrary.triggerBeforeHooks(self.getWorld(), function() {
117-
hookedUpFunction(function() {
118-
supportCodeLibrary.triggerAfterHooks(self.getWorld(), function() {
119-
callback();
120-
});
121-
});
122-
});
123-
}
124-
},
125-
126115
lookupStepDefinitionByName: function lookupStepDefinitionByName(stepName) {
127116
return supportCodeLibrary.lookupStepDefinitionByName(stepName);
128117
},

lib/cucumber/support_code/library.js

+12-16
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,8 @@ var Library = function(supportCodeDefinition) {
22
var MISSING_WORLD_INSTANCE_ERROR = "World constructor called back without World instance.";
33
var Cucumber = require('../../cucumber');
44

5-
var beforeHooks = Cucumber.Type.Collection();
6-
var afterHooks = Cucumber.Type.Collection();
75
var stepDefinitions = Cucumber.Type.Collection();
6+
var hooker = Cucumber.SupportCode.Library.Hooker();
87
var worldConstructor = Cucumber.SupportCode.WorldConstructor();
98

109
var self = {
@@ -24,26 +23,21 @@ var Library = function(supportCodeDefinition) {
2423
return (stepDefinition != undefined);
2524
},
2625

27-
defineBeforeHook: function defineBeforeHook(code) {
28-
var beforeHook = Cucumber.SupportCode.Hook(code);
29-
beforeHooks.add(beforeHook);
26+
hookUpFunctionWithWorld: function hookUpFunctionWithWorld(userFunction, world) {
27+
var hookedUpFunction = hooker.hookUpFunctionWithWorld(userFunction, world);
28+
return hookedUpFunction;
3029
},
3130

32-
triggerBeforeHooks: function(world, callback) {
33-
beforeHooks.forEach(function(beforeHook, callback) {
34-
beforeHook.invoke(world, callback);
35-
}, callback);
31+
defineAroundHook: function defineAroundHook(code) {
32+
hooker.addAroundHookCode(code);
3633
},
3734

38-
defineAfterHook: function defineAfterHook(code) {
39-
var afterHook = Cucumber.SupportCode.Hook(code);
40-
afterHooks.unshift(afterHook);
35+
defineBeforeHook: function defineBeforeHook(code) {
36+
hooker.addBeforeHookCode(code);
4137
},
4238

43-
triggerAfterHooks: function(world, callback) {
44-
afterHooks.forEach(function(afterHook, callback) {
45-
afterHook.invoke(world, callback);
46-
}, callback);
39+
defineAfterHook: function defineAfterHook(code) {
40+
hooker.addAfterHookCode(code);
4741
},
4842

4943
defineStep: function defineStep(name, code) {
@@ -64,6 +58,7 @@ var Library = function(supportCodeDefinition) {
6458
};
6559

6660
var supportCodeHelper = {
61+
Around : self.defineAroundHook,
6762
Before : self.defineBeforeHook,
6863
After : self.defineAfterHook,
6964
Given : self.defineStep,
@@ -77,4 +72,5 @@ var Library = function(supportCodeDefinition) {
7772

7873
return self;
7974
};
75+
Library.Hooker = require('./library/hooker');
8076
module.exports = Library;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
var Hooker = function() {
2+
var Cucumber = require('../../../cucumber');
3+
4+
var aroundHooks = Cucumber.Type.Collection();
5+
var beforeHooks = Cucumber.Type.Collection();
6+
var afterHooks = Cucumber.Type.Collection();
7+
8+
var self = {
9+
addAroundHookCode: function addAroundHookCode(code) {
10+
var aroundHook = Cucumber.SupportCode.Hook(code);
11+
aroundHooks.add(aroundHook);
12+
},
13+
14+
addBeforeHookCode: function addBeforeHookCode(code) {
15+
var beforeHook = Cucumber.SupportCode.Hook(code);
16+
beforeHooks.add(beforeHook);
17+
},
18+
19+
addAfterHookCode: function addAfterHookCode(code) {
20+
var afterHook = Cucumber.SupportCode.Hook(code);
21+
afterHooks.unshift(afterHook);
22+
},
23+
24+
hookUpFunctionWithWorld: function hookUpFunctionWithWorld(userFunction, world) {
25+
var hookedUpFunction = function(callback) {
26+
var postScenarioAroundHookCallbacks = Cucumber.Type.Collection();
27+
aroundHooks.forEach(callPreScenarioAroundHook, callBeforeHooks);
28+
29+
function callPreScenarioAroundHook(aroundHook, preScenarioAroundHookCallback) {
30+
aroundHook.invoke(world, function(postScenarioAroundHookCallback) {
31+
postScenarioAroundHookCallbacks.unshift(postScenarioAroundHookCallback);
32+
preScenarioAroundHookCallback();
33+
});
34+
}
35+
36+
function callBeforeHooks() {
37+
self.triggerBeforeHooks(world, callUserFunction);
38+
}
39+
40+
function callUserFunction() {
41+
userFunction(callAfterHooks);
42+
}
43+
44+
function callAfterHooks() {
45+
self.triggerAfterHooks(world, callPostScenarioAroundHooks);
46+
}
47+
48+
function callPostScenarioAroundHooks() {
49+
postScenarioAroundHookCallbacks.forEach(
50+
callPostScenarioAroundHook,
51+
callback
52+
);
53+
}
54+
55+
function callPostScenarioAroundHook(postScenarioAroundHookCallback, callback) {
56+
postScenarioAroundHookCallback.call(world, callback);
57+
}
58+
};
59+
return hookedUpFunction;
60+
},
61+
62+
triggerBeforeHooks: function triggerBeforeHooks(world, callback) {
63+
beforeHooks.forEach(function(beforeHook, callback) {
64+
beforeHook.invoke(world, callback);
65+
}, callback);
66+
},
67+
68+
triggerAfterHooks: function triggerAfterHooks(world, callback) {
69+
afterHooks.forEach(function(afterHook, callback) {
70+
afterHook.invoke(world, callback);
71+
}, callback);
72+
}
73+
};
74+
return self;
75+
};
76+
module.exports = Hooker;

lib/cucumber/type/collection.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ var Collection = function() {
2121
});
2222
};
2323
iterate();
24-
}
24+
},
25+
length: function length() { return items.length; }
2526
};
2627
return self;
2728
};

spec/cucumber/runtime/ast_tree_walker_spec.js

+19-17
Original file line numberDiff line numberDiff line change
@@ -162,10 +162,9 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
162162

163163
describe("visitScenario()", function() {
164164
var scenario, callback;
165-
var world;
166165

167166
beforeEach(function() {
168-
scenario = createSpyWithStubs("Scenario AST element", {acceptVisitor: null});
167+
scenario = createSpyWithStubs("scenario");
169168
callback = createSpy("Callback");
170169
spyOnStub(supportCodeLibrary, 'instantiateNewWorld');
171170
});
@@ -177,21 +176,22 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
177176
});
178177

179178
describe("on world instantiation completion", function() {
180-
var worldInstantiationCompletionCallback, world, event, payload, hookedUpFunction;
179+
var worldInstantiationCompletionCallback;
180+
var world, event, payload;
181+
var hookedUpScenarioVisit;
181182

182183
beforeEach(function() {
183-
world = createSpy("world instance");
184184
treeWalker.visitScenario(scenario, callback);
185185
worldInstantiationCompletionCallback = supportCodeLibrary.instantiateNewWorld.mostRecentCall.args[0];
186-
187-
event = createSpy("Event");
188-
payload = {scenario: scenario};
189-
scenarioVisitWithHooks = createSpy("scenario visit with hooks");
190-
spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event);
191-
spyOn(treeWalker, 'broadcastEventAroundUserFunction');
186+
world = createSpy("world instance");
187+
event = createSpy("scenario visit event");
188+
hookedUpScenarioVisit = createSpy("hooked up scenario visit");
189+
payload = {scenario: scenario};
192190
spyOn(treeWalker, 'setWorld');
193191
spyOn(treeWalker, 'witnessNewScenario');
194-
spyOn(treeWalker, 'hookUpFunction').andReturn(scenarioVisitWithHooks);
192+
spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event);
193+
spyOnStub(supportCodeLibrary, 'hookUpFunctionWithWorld').andReturn(hookedUpScenarioVisit);
194+
spyOn(treeWalker, 'broadcastEventAroundUserFunction');
195195
});
196196

197197
it("sets the new World instance", function() {
@@ -211,28 +211,30 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
211211

212212
it("hooks up a function", function() {
213213
worldInstantiationCompletionCallback(world);
214-
expect(treeWalker.hookUpFunction).toHaveBeenCalled();
215-
expect(treeWalker.hookUpFunction).toHaveBeenCalledWithAFunctionAsNthParameter(1);
214+
expect(supportCodeLibrary.hookUpFunctionWithWorld).toHaveBeenCalled();
215+
expect(supportCodeLibrary.hookUpFunctionWithWorld).toHaveBeenCalledWithAFunctionAsNthParameter(1);
216+
expect(supportCodeLibrary.hookUpFunctionWithWorld).toHaveBeenCalledWithValueAsNthParameter(world, 2);
216217
});
217218

218219
describe("hooked up function", function() {
219220
var hookedUpFunction, hookedUpFunctionCallback;
220221

221222
beforeEach(function() {
222-
hookedUpFunctionCallback = createSpy("hooked up function callback");
223223
worldInstantiationCompletionCallback(world);
224-
hookedUpFunction = treeWalker.hookUpFunction.mostRecentCall.args[0];
224+
hookedUpFunction = supportCodeLibrary.hookUpFunctionWithWorld.mostRecentCall.args[0];
225+
hookedUpFunctionCallback = createSpy("hooked up function callback");
226+
spyOnStub(scenario, 'acceptVisitor');
225227
});
226228

227-
it("tells the scenario to accept the tree walker itself as a visitor", function() {
229+
it("instructs the scenario to accept the tree walker as a visitor", function() {
228230
hookedUpFunction(hookedUpFunctionCallback);
229231
expect(scenario.acceptVisitor).toHaveBeenCalledWith(treeWalker, hookedUpFunctionCallback);
230232
});
231233
});
232234

233235
it("broadcasts the visit of the scenario", function() {
234236
worldInstantiationCompletionCallback(world);
235-
expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalledWith(event, scenarioVisitWithHooks, callback);
237+
expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalledWith(event, hookedUpScenarioVisit, callback);
236238
});
237239
});
238240
});

0 commit comments

Comments
 (0)