Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Before and After hooks. #31

Closed
wants to merge 14 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions features/step_definitions/cucumber_js_mappings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -195,10 +197,25 @@ def append_support_code(code)

def write_main_step_definitions_file
append_to_file(STEP_DEFINITIONS_FILE, "var fs = require('fs');\nvar stepDefinitions = function() {\n");
append_to_file(STEP_DEFINITIONS_FILE, world_code)
append_to_file(STEP_DEFINITIONS_FILE, support_code);
append_to_file(STEP_DEFINITIONS_FILE, "};\nmodule.exports = stepDefinitions;")
end

def world_code
<<-EOF
this.World = function() {
this.callCount = 0;
};

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 write_coffee_script_definition_file
append_to_file COFFEE_SCRIPT_DEFINITIONS_FILE, <<-EOF
fs = require('fs')
Expand Down
9 changes: 9 additions & 0 deletions features/step_definitions/cucumber_steps.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.touchStep('" + hookType + "-hook');\
callback();\
});\n";
callback();
});

Given(/^the step "([^"]*)" has a failing mapping$/, function(stepName, callback) {
this.stepDefinitions += "Given(/^" + stepName + "$/, function(callback) {\
world.touchStep(\"" + stepName + "\");\
Expand Down
25 changes: 25 additions & 0 deletions features/step_definitions/cucumber_steps.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@
write_coffee_script_definition_file
end

# TODO: encapsulate and move to cucumber-features
Given /^a passing (before|after) hook$/ do |hook_type|
define_hook = hook_type.capitalize
append_support_code <<-EOF
this.#{define_hook}(function(callback) {
this.logCycleEvent('#{hook_type}');
callback();
});
EOF
end

When /^Cucumber executes a scenario using that mapping$/ do
write_feature <<-EOF
Feature:
Expand All @@ -14,6 +25,20 @@
run_feature
end

# TODO: encapsulate and move to cucumber-features
When /^Cucumber executes a scenario$/ do
append_step_definition("a step", "this.logCycleEvent('step');\ncallback();")
scenario_with_steps "A scenario", "Given Cucumber executes a step definition"
run_feature
end

# TODO: encapsulate and move to cucumber-features
Then /^the (after|before) hook is fired (?:after|before) the scenario$/ do |hook_type|
expected_string = (hook_type == 'before' ? 'before -> step' : 'step -> after')
check_file_content(CucumberJsMappings::CYCLE_LOG_FILE, expected_string, true)
end

# TODO: encapsulate and move to cucumber-features
Then /^the mapping is run$/ do
assert_passed "a mapping"
end
1 change: 1 addition & 0 deletions features/step_definitions/cucumber_world.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ var proto = CucumberWorld.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);
Expand Down
12 changes: 11 additions & 1 deletion lib/cucumber/runtime/ast_tree_walker.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,17 @@ var AstTreeWalker = function(features, supportCodeLibrary, listeners) {
var event = AstTreeWalker.Event(AstTreeWalker.SCENARIO_EVENT_NAME, payload);
self.broadcastEventAroundUserFunction(
event,
function(callback) { scenario.acceptVisitor(self, callback); },
function(callback) {
supportCodeLibrary.triggerBeforeHooks(world, function() {
scenario.acceptVisitor(self, function() {
supportCodeLibrary.triggerAfterHooks(world, function() {
if (callback) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for this condition?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I want to say I originally added this to get a test passing, but not 100% sure. If that's the case then the test case should be improved and this should be removed.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm in the middle of a merge from master onto your branch and I removed this condition. It seems to work well.

callback();
}
});
});
});
},
callback
);
},
Expand Down
1 change: 1 addition & 0 deletions lib/cucumber/support_code.js
Original file line number Diff line number Diff line change
@@ -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');
Expand Down
11 changes: 11 additions & 0 deletions lib/cucumber/support_code/hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
var Hook = function(type, code) {
var Cucumber = require('../../cucumber');

var self = {
invoke: function(world, callback) {
code.apply(world, [callback]);
}
};
return self;
};
module.exports = Hook;
28 changes: 27 additions & 1 deletion lib/cucumber/support_code/library.js
Original file line number Diff line number Diff line change
@@ -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 = {
Expand All @@ -21,6 +23,28 @@ var Library = function(supportCodeDefinition) {
return (stepDefinition != undefined);
},

defineBeforeHook: function defineBeforeHook(code) {
var beforeHook = Cucumber.SupportCode.Hook('before', 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('after', 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);
Expand All @@ -32,6 +56,8 @@ var Library = function(supportCodeDefinition) {
};

var supportCodeHelper = {
Before : self.defineBeforeHook,
After : self.defineAfterHook,
Given : self.defineStep,
When : self.defineStep,
Then : self.defineStep,
Expand Down
1 change: 1 addition & 0 deletions lib/cucumber/type/collection.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
50 changes: 40 additions & 10 deletions spec/cucumber/runtime/ast_tree_walker_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
Expand Down Expand Up @@ -165,13 +165,16 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
var world;

beforeEach(function() {
scenario = createSpyWithStubs("Scenario AST element", {acceptVisitor: null});
callback = createSpy("Callback");
event = createSpy("Event");
payload = {scenario: scenario};
world = createSpy("world instance");
scenario = createSpyWithStubs("Scenario AST element", {acceptVisitor: null});
callback = createSpy("Callback");
event = createSpy("Event");
payload = {scenario: scenario};
world = createSpy("world instance");
triggerHookFake = function(world, callback) { callback(); };
spyOn(Cucumber.Runtime.AstTreeWalker, 'Event').andReturn(event);
spyOnStub(supportCodeLibrary, 'instantiateNewWorld').andReturn(world);
spyOnStub(supportCodeLibrary, 'triggerBeforeHooks').andCallFake(triggerHookFake);
spyOnStub(supportCodeLibrary, 'triggerAfterHooks').andCallFake(triggerHookFake);
spyOn(treeWalker, 'broadcastEventAroundUserFunction');
spyOn(treeWalker, 'witnessNewScenario');
spyOn(treeWalker, 'setWorld');
Expand Down Expand Up @@ -208,6 +211,27 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
toHaveBeenCalledWithValueAsNthParameter(callback, 3);
});

it("invokes before hooks", function() {
treeWalker.visitScenario(scenario, callback);
treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1]();

expect(supportCodeLibrary.triggerBeforeHooks).
toHaveBeenCalledWithValueAsNthParameter(world, 1);
expect(supportCodeLibrary.triggerBeforeHooks).
toHaveBeenCalledWithAFunctionAsNthParameter(2);
});

it("invokes after hooks", function() {
treeWalker.visitScenario(scenario, callback);
treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1]();
scenario.acceptVisitor.mostRecentCall.args[1]();

expect(supportCodeLibrary.triggerAfterHooks).
toHaveBeenCalledWithValueAsNthParameter(world, 1);
expect(supportCodeLibrary.triggerAfterHooks).
toHaveBeenCalledWithAFunctionAsNthParameter(2);
});

describe("user function", function() {
var userFunction, userFunctionCallback;

Expand All @@ -217,7 +241,13 @@ describe("Cucumber.Runtime.AstTreeWalker", function() {
userFunction = treeWalker.broadcastEventAroundUserFunction.mostRecentCall.args[1];
});

it("visits the scenario, passing it the received callback", function() {
it("visits the scenario, passing it the tree walker", function() {
userFunction(userFunctionCallback);
expect(scenario.acceptVisitor).
toHaveBeenCalledWithValueAsNthParameter(treeWalker, 1);
});

xit("visits the scenario, passing it the received callback", function() {
userFunction(userFunctionCallback);
expect(scenario.acceptVisitor).toHaveBeenCalledWith(treeWalker, userFunctionCallback);
});
Expand Down
Loading