Skip to content

Commit

Permalink
Add around hooks (#32)
Browse files Browse the repository at this point in the history
  • Loading branch information
jbpros committed Feb 22, 2012
1 parent e02b0b4 commit 2bc9537
Show file tree
Hide file tree
Showing 11 changed files with 508 additions and 143 deletions.
2 changes: 1 addition & 1 deletion features/cucumber-tck
Submodule cucumber-tck updated from 7b5b64 to 80eaf4
14 changes: 13 additions & 1 deletion features/step_definitions/cucumber_js_mappings.rb
Expand Up @@ -138,12 +138,24 @@ def write_world_function
def write_passing_hook hook_type def write_passing_hook hook_type
provide_cycle_logging_facilities provide_cycle_logging_facilities
define_hook = hook_type.capitalize define_hook = hook_type.capitalize
append_support_code <<-EOF if hook_type == "around"
append_support_code <<-EOF
this.#{define_hook}(function(runScenario) {
this.logCycleEvent('#{hook_type}-pre');
runScenario(function(callback) {
this.logCycleEvent('#{hook_type}-post');
callback();
});
});
EOF
else
append_support_code <<-EOF
this.#{define_hook}(function(callback) { this.#{define_hook}(function(callback) {
this.logCycleEvent('#{hook_type}'); this.logCycleEvent('#{hook_type}');
callback(); callback();
}); });
EOF EOF
end
end end


def write_scenario options = {} def write_scenario options = {}
Expand Down
25 changes: 23 additions & 2 deletions features/step_definitions/cucumber_steps.js
Expand Up @@ -27,6 +27,17 @@ var cucumberSteps = function() {
callback(); callback();
}); });


Given(/^a passing around hook$/, function(callback) {
this.stepDefinitions += "this.Around(function(runScenario) {\
world.logCycleEvent('around-pre');\
runScenario(function(callback) {\
world.logCycleEvent('around-post');\
callback();\
});\
});\n";
callback();
});

Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) { Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) {
this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\ this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\
world.touchStep(\"" + stepName + "\");\ world.touchStep(\"" + stepName + "\");\
Expand Down Expand Up @@ -240,9 +251,19 @@ callback();\


Then(/^the (before|after) hook is fired (?:before|after) the scenario$/, function(hookType, callback) { Then(/^the (before|after) hook is fired (?:before|after) the scenario$/, function(hookType, callback) {
if (hookType == 'before') if (hookType == 'before')
this.assertCycleSequence(hookType, 'step'); this.assertCycleSequence(hookType, 'step 1');
else else
this.assertCycleSequence('step', hookType); this.assertCycleSequence('step 1', hookType);
callback();
});

Then(/^the around hook fires around the scenario$/, function(callback) {
this.assertCycleSequence('around-pre', 'step 1', 'around-post');
callback();
});

Then(/^the around hook is fired around the other hooks$/, function(callback) {
this.assertCycleSequence('around-pre', 'before', 'step 1', 'after', 'around-post');
callback(); callback();
}); });


Expand Down
2 changes: 1 addition & 1 deletion features/step_definitions/cucumber_world.js
Expand Up @@ -49,7 +49,7 @@ proto.runFeatureWithSupportCodeSource = function runFeatureWithSupportCodeSource
proto.runAScenario = function runAScenario(callback) { proto.runAScenario = function runAScenario(callback) {
this.addScenario("", "Given a step"); this.addScenario("", "Given a step");
this.stepDefinitions += "Given(/^a step$/, function(callback) {\ this.stepDefinitions += "Given(/^a step$/, function(callback) {\
world.logCycleEvent('step');\ world.logCycleEvent('step 1');\
callback();\ callback();\
});"; });";
this.runFeature({}, callback); this.runFeature({}, callback);
Expand Down
21 changes: 5 additions & 16 deletions lib/cucumber/runtime/ast_tree_walker.js
Expand Up @@ -43,10 +43,11 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
supportCodeLibrary.instantiateNewWorld(function(world) { supportCodeLibrary.instantiateNewWorld(function(world) {
self.setWorld(world); self.setWorld(world);
self.witnessNewScenario(); self.witnessNewScenario();
var payload = { scenario: scenario }; var payload = { scenario: scenario };
var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload); var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload);
var hookedUpScenarioVisit = self.hookUpFunction( var hookedUpScenarioVisit = supportCodeLibrary.hookUpFunctionWithWorld(
function(callback) { scenario.acceptVisitor(self, callback); } function(callback) { scenario.acceptVisitor(self, callback); },
world
); );
self.broadcastEventAroundUserFunction( self.broadcastEventAroundUserFunction(
event, event,
Expand Down Expand Up @@ -111,18 +112,6 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
); );
}, },


hookUpFunction: function hookUpFunction(hookedUpFunction) {
return function(callback) {
supportCodeLibrary.triggerBeforeHooks(self.getWorld(), function() {
hookedUpFunction(function() {
supportCodeLibrary.triggerAfterHooks(self.getWorld(), function() {
callback();
});
});
});
}
},

lookupStepDefinitionByName: function lookupStepDefinitionByName(stepName) { lookupStepDefinitionByName: function lookupStepDefinitionByName(stepName) {
return supportCodeLibrary.lookupStepDefinitionByName(stepName); return supportCodeLibrary.lookupStepDefinitionByName(stepName);
}, },
Expand Down
28 changes: 12 additions & 16 deletions lib/cucumber/support_code/library.js
Expand Up @@ -2,9 +2,8 @@ var Library = function(supportCodeDefinition) {
var MISSING_WORLD_INSTANCE_ERROR = "World constructor called back without World instance."; var MISSING_WORLD_INSTANCE_ERROR = "World constructor called back without World instance.";
var Cucumber = require('../../cucumber'); var Cucumber = require('../../cucumber');


var beforeHooks = Cucumber.Type.Collection();
var afterHooks = Cucumber.Type.Collection();
var stepDefinitions = Cucumber.Type.Collection(); var stepDefinitions = Cucumber.Type.Collection();
var hooker = Cucumber.SupportCode.Library.Hooker();
var worldConstructor = Cucumber.SupportCode.WorldConstructor(); var worldConstructor = Cucumber.SupportCode.WorldConstructor();


var self = { var self = {
Expand All @@ -24,26 +23,21 @@ var Library = function(supportCodeDefinition) {
return (stepDefinition != undefined); return (stepDefinition != undefined);
}, },


defineBeforeHook: function defineBeforeHook(code) { hookUpFunctionWithWorld: function hookUpFunctionWithWorld(userFunction, world) {
var beforeHook = Cucumber.SupportCode.Hook(code); var hookedUpFunction = hooker.hookUpFunctionWithWorld(userFunction, world);
beforeHooks.add(beforeHook); return hookedUpFunction;
}, },


triggerBeforeHooks: function(world, callback) { defineAroundHook: function defineAroundHook(code) {
beforeHooks.forEach(function(beforeHook, callback) { hooker.addAroundHookCode(code);
beforeHook.invoke(world, callback);
}, callback);
}, },


defineAfterHook: function defineAfterHook(code) { defineBeforeHook: function defineBeforeHook(code) {
var afterHook = Cucumber.SupportCode.Hook(code); hooker.addBeforeHookCode(code);
afterHooks.unshift(afterHook);
}, },


triggerAfterHooks: function(world, callback) { defineAfterHook: function defineAfterHook(code) {
afterHooks.forEach(function(afterHook, callback) { hooker.addAfterHookCode(code);
afterHook.invoke(world, callback);
}, callback);
}, },


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


var supportCodeHelper = { var supportCodeHelper = {
Around : self.defineAroundHook,
Before : self.defineBeforeHook, Before : self.defineBeforeHook,
After : self.defineAfterHook, After : self.defineAfterHook,
Given : self.defineStep, Given : self.defineStep,
Expand All @@ -77,4 +72,5 @@ var Library = function(supportCodeDefinition) {


return self; return self;
}; };
Library.Hooker = require('./library/hooker');
module.exports = Library; module.exports = Library;
76 changes: 76 additions & 0 deletions lib/cucumber/support_code/library/hooker.js
@@ -0,0 +1,76 @@
var Hooker = function() {
var Cucumber = require('../../../cucumber');

var aroundHooks = Cucumber.Type.Collection();
var beforeHooks = Cucumber.Type.Collection();
var afterHooks = Cucumber.Type.Collection();

var self = {
addAroundHookCode: function addAroundHookCode(code) {
var aroundHook = Cucumber.SupportCode.Hook(code);
aroundHooks.add(aroundHook);
},

addBeforeHookCode: function addBeforeHookCode(code) {
var beforeHook = Cucumber.SupportCode.Hook(code);
beforeHooks.add(beforeHook);
},

addAfterHookCode: function addAfterHookCode(code) {
var afterHook = Cucumber.SupportCode.Hook(code);
afterHooks.unshift(afterHook);
},

hookUpFunctionWithWorld: function hookUpFunctionWithWorld(userFunction, world) {
var hookedUpFunction = function(callback) {
var postScenarioAroundHookCallbacks = Cucumber.Type.Collection();
aroundHooks.forEach(callPreScenarioAroundHook, callBeforeHooks);

function callPreScenarioAroundHook(aroundHook, preScenarioAroundHookCallback) {
aroundHook.invoke(world, function(postScenarioAroundHookCallback) {
postScenarioAroundHookCallbacks.unshift(postScenarioAroundHookCallback);
preScenarioAroundHookCallback();
});
}

function callBeforeHooks() {
self.triggerBeforeHooks(world, callUserFunction);
}

function callUserFunction() {
userFunction(callAfterHooks);
}

function callAfterHooks() {
self.triggerAfterHooks(world, callPostScenarioAroundHooks);
}

function callPostScenarioAroundHooks() {
postScenarioAroundHookCallbacks.forEach(
callPostScenarioAroundHook,
callback
);
}

function callPostScenarioAroundHook(postScenarioAroundHookCallback, callback) {
postScenarioAroundHookCallback.call(world, callback);
}
};
return hookedUpFunction;
},

triggerBeforeHooks: function triggerBeforeHooks(world, callback) {
beforeHooks.forEach(function(beforeHook, callback) {
beforeHook.invoke(world, callback);
}, callback);
},

triggerAfterHooks: function triggerAfterHooks(world, callback) {
afterHooks.forEach(function(afterHook, callback) {
afterHook.invoke(world, callback);
}, callback);
}
};
return self;
};
module.exports = Hooker;
3 changes: 2 additions & 1 deletion lib/cucumber/type/collection.js
Expand Up @@ -21,7 +21,8 @@ var Collection = function() {
}); });
}; };
iterate(); iterate();
} },
length: function length() { return items.length; }
}; };
return self; return self;
}; };
Expand Down
36 changes: 19 additions & 17 deletions spec/cucumber/runtime/ast_tree_walker_spec.js
Expand Up @@ -162,10 +162,9 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {


describe("visitScenario()", function() { describe("visitScenario()", function() {
var scenario, callback; var scenario, callback;
var world;


beforeEach(function() { beforeEach(function() {
scenario = createSpyWithStubs("Scenario AST element", {acceptVisitor: null}); scenario = createSpyWithStubs("scenario");
callback = createSpy("Callback"); callback = createSpy("Callback");
spyOnStub(supportCodeLibrary, 'instantiateNewWorld'); spyOnStub(supportCodeLibrary, 'instantiateNewWorld');
}); });
Expand All @@ -177,21 +176,22 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
}); });


describe("on world instantiation completion", function() { describe("on world instantiation completion", function() {
var worldInstantiationCompletionCallback, world, event, payload, hookedUpFunction; var worldInstantiationCompletionCallback;
var world, event, payload;
var hookedUpScenarioVisit;


beforeEach(function() { beforeEach(function() {
world = createSpy("world instance");
treeWalker.visitScenario(scenario, callback); treeWalker.visitScenario(scenario, callback);
worldInstantiationCompletionCallback = supportCodeLibrary.instantiateNewWorld.mostRecentCall.args[0]; worldInstantiationCompletionCallback = supportCodeLibrary.instantiateNewWorld.mostRecentCall.args[0];

world = createSpy("world instance");
event = createSpy("Event"); event = createSpy("scenario visit event");
payload = {scenario: scenario}; hookedUpScenarioVisit = createSpy("hooked up scenario visit");
scenarioVisitWithHooks = createSpy("scenario visit with hooks"); payload = {scenario: scenario};
spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event);
spyOn(treeWalker, 'broadcastEventAroundUserFunction');
spyOn(treeWalker, 'setWorld'); spyOn(treeWalker, 'setWorld');
spyOn(treeWalker, 'witnessNewScenario'); spyOn(treeWalker, 'witnessNewScenario');
spyOn(treeWalker, 'hookUpFunction').andReturn(scenarioVisitWithHooks); spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event);
spyOnStub(supportCodeLibrary, 'hookUpFunctionWithWorld').andReturn(hookedUpScenarioVisit);
spyOn(treeWalker, 'broadcastEventAroundUserFunction');
}); });


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


it("hooks up a function", function() { it("hooks up a function", function() {
worldInstantiationCompletionCallback(world); worldInstantiationCompletionCallback(world);
expect(treeWalker.hookUpFunction).toHaveBeenCalled(); expect(supportCodeLibrary.hookUpFunctionWithWorld).toHaveBeenCalled();
expect(treeWalker.hookUpFunction).toHaveBeenCalledWithAFunctionAsNthParameter(1); expect(supportCodeLibrary.hookUpFunctionWithWorld).toHaveBeenCalledWithAFunctionAsNthParameter(1);
expect(supportCodeLibrary.hookUpFunctionWithWorld).toHaveBeenCalledWithValueAsNthParameter(world, 2);
}); });


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


beforeEach(function() { beforeEach(function() {
hookedUpFunctionCallback = createSpy("hooked up function callback");
worldInstantiationCompletionCallback(world); worldInstantiationCompletionCallback(world);
hookedUpFunction = treeWalker.hookUpFunction.mostRecentCall.args[0]; hookedUpFunction = supportCodeLibrary.hookUpFunctionWithWorld.mostRecentCall.args[0];
hookedUpFunctionCallback = createSpy("hooked up function callback");
spyOnStub(scenario, 'acceptVisitor');
}); });


it("tells the scenario to accept the tree walker itself as a visitor", function() { it("instructs the scenario to accept the tree walker as a visitor", function() {
hookedUpFunction(hookedUpFunctionCallback); hookedUpFunction(hookedUpFunctionCallback);
expect(scenario.acceptVisitor).toHaveBeenCalledWith(treeWalker, hookedUpFunctionCallback); expect(scenario.acceptVisitor).toHaveBeenCalledWith(treeWalker, hookedUpFunctionCallback);
}); });
}); });


it("broadcasts the visit of the scenario", function() { it("broadcasts the visit of the scenario", function() {
worldInstantiationCompletionCallback(world); worldInstantiationCompletionCallback(world);
expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalledWith(event, scenarioVisitWithHooks, callback); expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalledWith(event, hookedUpScenarioVisit, callback);
}); });
}); });
}); });
Expand Down

0 comments on commit 2bc9537

Please sign in to comment.