Permalink
Browse files

Add Before/After hooks (#32, close #31)

  • Loading branch information...
tristandunn authored and jbpros committed Jan 18, 2012
1 parent e956a14 commit 2c3b5e2f0c3c2418ba5cdbac17c92ee9792bb3b5
@@ -5,6 +5,8 @@ module CucumberJsMappings
WORLD_VARIABLE_LOG_FILE = "world_variable.log"
WORLD_FUNCTION_LOG_FILE = "world_function.log"
DATA_TABLE_LOG_FILE = "data_table.log"
+ CYCLE_LOG_FILE = "cycle.log"
+
attr_accessor :support_code
def features_dir
@@ -108,6 +110,36 @@ def write_world_function
EOF
end
+ def write_passing_hook hook_type
+ provide_cycle_logging_facilities
+ define_hook = hook_type.capitalize
+ append_support_code <<-EOF
+this.#{define_hook}(function(callback) {
+ this.logCycleEvent('#{hook_type}');
+ callback();
+});
+EOF
+ end
+
+ def write_scenario
+ provide_cycle_logging_facilities
+ append_step_definition("a step", "this.logCycleEvent('step');\ncallback();")
+ scenario_with_steps("A scenario", "Given a step")
+ end
+
+ def provide_cycle_logging_facilities
+ return if @cycle_logging_facilities_ready
+
+ @cycle_logging_facilities_ready = true
+ append_support_code <<-EOF
+ this.World.prototype.logCycleEvent = function logCycleEvent(name) {
+ fd = fs.openSync('#{CYCLE_LOG_FILE}', 'a');
+ fs.writeSync(fd, " -> " + name, null);
+ fs.closeSync(fd);
+ };
+EOF
+ end
+
def assert_passing_scenario
assert_partial_output("1 scenario (1 passed)", all_output)
assert_success true
@@ -145,6 +177,11 @@ def assert_world_function_called
check_file_presence [WORLD_FUNCTION_LOG_FILE], true
end
+ def assert_cycle_sequence *args
+ expected_string = args.join " -> "
+ check_file_content(CucumberJsMappings::CYCLE_LOG_FILE, expected_string, true)
+ end
+
def assert_data_table_equals_json(json)
prep_for_fs_check do
log_file_contents = IO.read(DATA_TABLE_LOG_FILE)
@@ -154,15 +191,15 @@ def assert_data_table_equals_json(json)
end
end
- def assert_suggested_step_definition_snippet(stepdef_type, stepdef_name, parameter_count = 0, doc_string = false, data_table = false)
+ def assert_suggested_step_definition_snippet(stepdef_keyword, stepdef_pattern, parameter_count = 0, doc_string = false, data_table = false)
parameter_count ||= 0
params = Array.new(parameter_count) { |i| "arg#{i+1}" }
params << "string" if doc_string
params << "table" if data_table
params << "callback"
params = params.join ", "
expected_snippet = <<-EOF
-this.#{stepdef_type}(/#{stepdef_name}/, function(#{params}) {
+this.#{stepdef_keyword}(/#{stepdef_pattern}/, function(#{params}) {
// express the regexp above with the code you wish you had
callback.pending();
});
@@ -18,6 +18,15 @@ var cucumberSteps = function() {
callback();
});
+ Given(/^a passing (before|after) hook$/, function(hookType, callback) {
+ var defineHook = (hookType == 'before' ? 'Before' : 'After');
+ this.stepDefinitions += defineHook + "(function(callback) {\
+ world.logCycleEvent('" + hookType + "');\
+ callback();\
+});\n";
+ callback();
+ });
+
Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) {
this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\
world.touchStep(\"" + stepName + "\");\
@@ -58,7 +67,7 @@ var cucumberSteps = function() {
Given(/^the following data table in a step:$/, function(dataTable, callback) {
this.featureSource += "Feature:\n";
this.featureSource += " Scenario:\n";
- this.featureSource += " When a step with data table:\n"
+ this.featureSource += " When a step with data table:\n";
this.featureSource += dataTable.replace(/^/gm, ' ');
callback();
});
@@ -67,6 +76,10 @@ var cucumberSteps = function() {
this.runFeature(callback);
});
+ When(/^Cucumber executes a scenario$/, function(callback) {
+ this.runAScenario(callback);
+ });
+
When(/^Cucumber runs the feature$/, function(callback) {
this.runFeature(callback);
});
@@ -146,5 +159,13 @@ var cucumberSteps = function() {
this.assertEqual(expectedDataTable, World.mostRecentInstance.dataTableLog);
callback();
});
+
+ Then(/^the (before|after) hook is fired (?:before|after) the scenario$/, function(hookType, callback) {
+ if (hookType == 'before')
+ this.assertCycleSequence(hookType, 'step');
+ else
+ this.assertCycleSequence('step', hookType);
+ callback();
+ });
};
module.exports = cucumberSteps;
@@ -3,6 +3,7 @@ var World = function(callback) {
this.featureSource = "";
this.stepDefinitions = "";
this.runOutput = "";
+ this.cycleEvents = "";
this.runSucceeded = false;
World.mostRecentInstance = this;
callback(this);
@@ -13,6 +14,7 @@ var proto = World.prototype;
proto.runFeature = function runFeature(callback) {
var supportCode;
var supportCodeSource = "supportCode = function() {\n var Given = When = Then = this.defineStep;\n" +
+ " var Before = this.Before, After = this.After;\n" +
this.stepDefinitions + "};\n";
var world = this;
eval(supportCodeSource);
@@ -33,6 +35,21 @@ proto.runFeatureWithSupportCodeSource = function runFeatureWithSupportCodeSource
});
}
+proto.runAScenario = function runAScenario(callback) {
+ this.featureSource += "Feature:\n";
+ this.featureSource += " Scenario:\n";
+ this.featureSource += " Given a step\n";
+ this.stepDefinitions += "Given(/^a step$/, function(callback) {\
+ world.logCycleEvent('step');\
+ callback();\
+});";
+ this.runFeature(callback);
+}
+
+proto.logCycleEvent = function logCycleEvent(event) {
+ this.cycleEvents += " -> " + event;
+}
+
proto.touchStep = function touchStep(string) {
this.touchedSteps.push(string);
}
@@ -117,4 +134,10 @@ proto.assertEqual = function assertRawDataTable(expected, actual) {
throw(new Error("Expected:\n\"" + actualJSON + "\"\nto match:\n\"" + expectedJSON + "\""));
}
+proto.assertCycleSequence = function assertCycleSequence(first, second) {
+ var partialSequence = first + ' -> ' + second;
+ if (this.cycleEvents.indexOf(partialSequence) < 0)
+ throw(new Error("Expected cycle sequence \"" + this.cycleEvents + "\" to contain \"" + partialSequence + "\""));
+}
+
exports.World = World;
@@ -41,11 +41,14 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
supportCodeLibrary.instantiateNewWorld(function(world) {
self.setWorld(world);
self.witnessNewScenario();
- var payload = { scenario: scenario };
- var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload);
+ var payload = { scenario: scenario };
+ var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload);
+ var hookedUpScenarioVisit = self.hookUpFunction(
+ function(callback) { scenario.acceptVisitor(self, callback); }
+ );
self.broadcastEventAroundUserFunction(
event,
- function(callback) { scenario.acceptVisitor(self, callback); },
+ hookedUpScenarioVisit,
callback
);
});
@@ -105,6 +108,18 @@ 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) {
return supportCodeLibrary.lookupStepDefinitionByName(stepName);
},
@@ -1,4 +1,5 @@
var SupportCode = {};
+SupportCode.Hook = require('./support_code/hook');
SupportCode.Library = require('./support_code/library');
SupportCode.StepDefinition = require('./support_code/step_definition');
SupportCode.StepDefinitionSnippetBuilder = require('./support_code/step_definition_snippet_builder');
@@ -0,0 +1,11 @@
+var Hook = function(code) {
+ var Cucumber = require('../../cucumber');
+
+ var self = {
+ invoke: function(world, callback) {
+ code.call(world, callback);
+ }
+ };
+ return self;
+};
+module.exports = Hook;
@@ -1,7 +1,9 @@
var Library = function(supportCodeDefinition) {
var Cucumber = require('../../cucumber');
- var stepDefinitions = Cucumber.Type.Collection();
+ var beforeHooks = Cucumber.Type.Collection();
+ var afterHooks = Cucumber.Type.Collection();
+ var stepDefinitions = Cucumber.Type.Collection();
var worldConstructor = Cucumber.SupportCode.WorldConstructor();
var self = {
@@ -21,6 +23,28 @@ var Library = function(supportCodeDefinition) {
return (stepDefinition != undefined);
},
+ defineBeforeHook: function defineBeforeHook(code) {
+ var beforeHook = Cucumber.SupportCode.Hook(code);
+ beforeHooks.add(beforeHook);
+ },
+
+ triggerBeforeHooks: function(world, callback) {
+ beforeHooks.forEach(function(beforeHook, callback) {
+ beforeHook.invoke(world, callback);
+ }, callback);
+ },
+
+ defineAfterHook: function defineAfterHook(code) {
+ var afterHook = Cucumber.SupportCode.Hook(code);
+ afterHooks.unshift(afterHook);
+ },
+
+ triggerAfterHooks: function(world, callback) {
+ afterHooks.forEach(function(afterHook, callback) {
+ afterHook.invoke(world, callback);
+ }, callback);
+ },
+
defineStep: function defineStep(name, code) {
var stepDefinition = Cucumber.SupportCode.StepDefinition(name, code);
stepDefinitions.add(stepDefinition);
@@ -36,6 +60,8 @@ var Library = function(supportCodeDefinition) {
};
var supportCodeHelper = {
+ Before : self.defineBeforeHook,
+ After : self.defineAfterHook,
Given : self.defineStep,
When : self.defineStep,
Then : self.defineStep,
@@ -2,6 +2,7 @@ var Collection = function() {
var items = new Array();
var self = {
add: function add(item) { items.push(item); },
+ unshift: function unshift(item) { items.unshift(item); },
getLast: function getLast() { return items[items.length-1]; },
syncForEach: function syncForEach(userFunction) { items.forEach(userFunction); },
forEach: function forEach(userFunction, callback) {
@@ -97,10 +97,10 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
var feature, callback, event, payload;
beforeEach(function() {
- feature = createSpyWithStubs("Feature AST element", {acceptVisitor: null});
- callback = createSpy("Callback");
- event = createSpy("Event");
- payload = {feature: feature};
+ feature = createSpyWithStubs("Feature AST element", {acceptVisitor: null});
+ callback = createSpy("Callback");
+ event = createSpy("Event");
+ payload = {feature: feature};
spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event);
spyOn(treeWalker, 'broadcastEventAroundUserFunction');
});
@@ -177,19 +177,21 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
});
describe("on world instantiation completion", function() {
- var worldInstantiationCompletionCallback, world, event, payload;
+ var worldInstantiationCompletionCallback, world, event, payload, hookedUpFunction;
beforeEach(function() {
world = createSpy("world instance");
treeWalker.visitScenario(scenario, callback);
worldInstantiationCompletionCallback = supportCodeLibrary.instantiateNewWorld.mostRecentCall.args[0];
- event = createSpy("Event");
- payload = {scenario: scenario};
+ event = createSpy("Event");
+ payload = {scenario: scenario};
+ scenarioVisitWithHooks = createSpy("scenario visit with hooks");
spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event);
spyOn(treeWalker, 'broadcastEventAroundUserFunction');
spyOn(treeWalker, 'setWorld');
spyOn(treeWalker, 'witnessNewScenario');
+ spyOn(treeWalker, 'hookUpFunction').andReturn(scenarioVisitWithHooks);
});
it("sets the new World instance", function() {
@@ -207,31 +209,31 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
expect(Cucumber.Runtime.AstTreeWalker.Event).toHaveBeenCalledWith(Cucumber.Runtime.AstTreeWalker.SCENARIO_EVENT_NAME, payload);
});
- it("broadcasts the visit of the scenario", function() {
+ it("hooks up a function", function() {
worldInstantiationCompletionCallback(world);
- expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalled();
- expect(treeWalker.broadcastEventAroundUserFunction).
- toHaveBeenCalledWithValueAsNthParameter(event, 1);
- expect(treeWalker.broadcastEventAroundUserFunction).
- toHaveBeenCalledWithAFunctionAsNthParameter(2);
- expect(treeWalker.broadcastEventAroundUserFunction).
- toHaveBeenCalledWithValueAsNthParameter(callback, 3);
+ expect(treeWalker.hookUpFunction).toHaveBeenCalled();
+ expect(treeWalker.hookUpFunction).toHaveBeenCalledWithAFunctionAsNthParameter(1);
});
- describe("user function", function() {
- var userFunction, userFunctionCallback;
+ describe("hooked up function", function() {
+ var hookedUpFunction, hookedUpFunctionCallback;
beforeEach(function() {
- userFunctionCallback = createSpy("User function callback");
+ hookedUpFunctionCallback = createSpy("hooked up function callback");
worldInstantiationCompletionCallback(world);
- userFunction = treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1];
+ hookedUpFunction = treeWalker.hookUpFunction.mostRecentCall.args[0];
});
- it("visits the scenario, passing it the received callback", function() {
- userFunction(userFunctionCallback);
- expect(scenario.acceptVisitor).toHaveBeenCalledWith(treeWalker, userFunctionCallback);
+ it("tells the scenario to accept the tree walker itself as a visitor", function() {
+ hookedUpFunction(hookedUpFunctionCallback);
+ expect(scenario.acceptVisitor).toHaveBeenCalledWith(treeWalker, hookedUpFunctionCallback);
});
});
+
+ it("broadcasts the visit of the scenario", function() {
+ worldInstantiationCompletionCallback(world);
+ expect(treeWalker.broadcastEventAroundUserFunction).toHaveBeenCalledWith(event, scenarioVisitWithHooks, callback);
+ });
});
});
Oops, something went wrong.

0 comments on commit 2c3b5e2

Please sign in to comment.