Skip to content

Commit e328551

Browse files
authored
Adds before and after step hooks (#145)
1 parent 2c9f857 commit e328551

File tree

4 files changed

+337
-2
lines changed

4 files changed

+337
-2
lines changed

cucumber-tsflow-specs/features/hooks.feature

Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,3 +282,294 @@ Feature: Support for Cucumber hooks
282282
Then it passes
283283
And the hook "setup environment" was executed on scenario "example"
284284
And the hook "tear down environment" was executed on scenario "example"
285+
286+
Scenario: Binding a before step hook
287+
Given a file named "features/a.feature" with:
288+
"""feature
289+
Feature: Feature
290+
Scenario: example
291+
Given a step
292+
"""
293+
And a file named "step_definitions/steps.ts" with:
294+
"""ts
295+
import {binding, given, beforeStep} from 'cucumber-tsflow';
296+
297+
@binding()
298+
class Steps {
299+
private state = 'hook has not executed';
300+
301+
@beforeStep()
302+
public hook() {
303+
this.state = 'hook has executed';
304+
}
305+
306+
@given("a step")
307+
public given() {
308+
console.log(this.state);
309+
}
310+
}
311+
312+
export = Steps;
313+
"""
314+
When I run cucumber-js
315+
Then it passes
316+
And the output does not contain "hook has not executed"
317+
And the output contains "hook has executed"
318+
319+
Scenario: Binding a before step hook with multiple steps
320+
Given a file named "features/a.feature" with:
321+
"""feature
322+
Feature: Feature
323+
Scenario: example
324+
Given a step
325+
And another step
326+
"""
327+
And a file named "step_definitions/steps.ts" with:
328+
"""ts
329+
import {binding, given, beforeStep, when} from 'cucumber-tsflow';
330+
331+
@binding()
332+
class Steps {
333+
private counter = 0;
334+
335+
@beforeStep()
336+
public hook() {
337+
console.log(`${this.counter++} execution: beforeStep`);
338+
}
339+
340+
@given("a step")
341+
public given() {
342+
console.log(`${this.counter++} execution: given`);
343+
}
344+
345+
@when("another step")
346+
public when() {
347+
console.log(`${this.counter++} execution: when`);
348+
}
349+
}
350+
351+
export = Steps;
352+
"""
353+
When I run cucumber-js
354+
Then it passes
355+
And the output does not contain "hook has not executed"
356+
And the output contains text:
357+
"""
358+
.0 execution: beforeStep
359+
1 execution: given
360+
.2 execution: beforeStep
361+
3 execution: when
362+
"""
363+
364+
Scenario: Binding multiple before step hooks
365+
Given a file named "features/a.feature" with:
366+
"""feature
367+
Feature: Feature
368+
Scenario: example
369+
Given a step
370+
"""
371+
And a file named "step_definitions/steps.ts" with:
372+
"""ts
373+
import {binding, given, beforeStep} from 'cucumber-tsflow';
374+
375+
@binding()
376+
class Steps {
377+
private state = 'no hook executed';
378+
379+
@beforeStep()
380+
public hookOne() {
381+
console.log(this.state)
382+
this.state = 'hook one has executed';
383+
}
384+
385+
@beforeStep()
386+
public hookTwo() {
387+
console.log(this.state)
388+
this.state = 'hook two has executed';
389+
}
390+
391+
@given("a step")
392+
public given() {
393+
console.log(this.state);
394+
this.state = 'step has executed';
395+
}
396+
}
397+
398+
export = Steps;
399+
"""
400+
When I run cucumber-js
401+
Then it passes
402+
And the output does not contain "step has not executed"
403+
And the output contains text:
404+
"""
405+
.no hook executed
406+
hook one has executed
407+
hook two has executed
408+
"""
409+
410+
Scenario: Binding an after step hook
411+
Given a file named "features/a.feature" with:
412+
"""feature
413+
Feature: Feature
414+
Scenario: example
415+
Given a step
416+
"""
417+
And a file named "step_definitions/steps.ts" with:
418+
"""ts
419+
import {binding, given, afterStep} from 'cucumber-tsflow';
420+
421+
@binding()
422+
class Steps {
423+
private state = 'step has not executed';
424+
425+
@afterStep()
426+
public hook() {
427+
console.log(this.state);
428+
}
429+
430+
@given("a step")
431+
public given() {
432+
this.state = 'step has executed';
433+
}
434+
}
435+
436+
export = Steps;
437+
"""
438+
When I run cucumber-js
439+
Then it passes
440+
And the output does not contain "step has not executed"
441+
And the output contains "step has executed"
442+
443+
Scenario: Binding an after step hook with multiple steps
444+
Given a file named "features/a.feature" with:
445+
"""feature
446+
Feature: Feature
447+
Scenario: example
448+
Given a step
449+
And another step
450+
"""
451+
And a file named "step_definitions/steps.ts" with:
452+
"""ts
453+
import {binding, given, afterStep, when} from 'cucumber-tsflow';
454+
455+
@binding()
456+
class Steps {
457+
private counter = 0;
458+
459+
@afterStep()
460+
public hook() {
461+
console.log(`${this.counter++} execution: afterStep`);
462+
}
463+
464+
@given("a step")
465+
public given() {
466+
console.log(`${this.counter++} execution: given`);
467+
}
468+
469+
@when("another step")
470+
public when() {
471+
console.log(`${this.counter++} execution: when`);
472+
}
473+
}
474+
475+
export = Steps;
476+
"""
477+
When I run cucumber-js
478+
Then it passes
479+
And the output does not contain "step has not executed"
480+
And the output contains text:
481+
"""
482+
.0 execution: given
483+
1 execution: afterStep
484+
.2 execution: when
485+
3 execution: afterStep
486+
"""
487+
488+
Scenario: Binding multiple after step hooks
489+
Given a file named "features/a.feature" with:
490+
"""feature
491+
Feature: Feature
492+
Scenario: example
493+
Given a step
494+
"""
495+
And a file named "step_definitions/steps.ts" with:
496+
"""ts
497+
import {binding, given, afterStep} from 'cucumber-tsflow';
498+
499+
@binding()
500+
class Steps {
501+
private state = 'no hook executed';
502+
503+
@given("a step")
504+
public given() {
505+
console.log(this.state)
506+
this.state = 'step has executed';
507+
}
508+
509+
@afterStep()
510+
public hookOne() {
511+
console.log(this.state)
512+
this.state = 'hook one has executed';
513+
}
514+
515+
@afterStep()
516+
public hookTwo() {
517+
console.log(this.state);
518+
this.state = 'hook two has executed';
519+
}
520+
}
521+
522+
export = Steps;
523+
"""
524+
When I run cucumber-js
525+
Then it passes
526+
And the output does not contain "step has not executed"
527+
And the output contains text:
528+
"""
529+
.no hook executed
530+
step has executed
531+
hook two has executed
532+
"""
533+
534+
Scenario: Binding before and after step hooks
535+
Given a file named "features/a.feature" with:
536+
"""feature
537+
Feature: Feature
538+
Scenario: example
539+
Given a step
540+
"""
541+
And a file named "step_definitions/steps.ts" with:
542+
"""ts
543+
import {binding, given, beforeStep, afterStep} from 'cucumber-tsflow';
544+
545+
@binding()
546+
class Steps {
547+
private state = 'hook has not executed';
548+
549+
@beforeStep()
550+
public beforeStep() {
551+
this.state = 'before hook executed';
552+
}
553+
554+
@given("a step")
555+
public given() {
556+
console.log(this.state);
557+
this.state = 'step has executed';
558+
}
559+
560+
@afterStep()
561+
public afterStep() {
562+
console.log(this.state);
563+
}
564+
}
565+
566+
export = Steps;
567+
"""
568+
When I run cucumber-js
569+
Then it passes
570+
And the output does not contain "step has not executed"
571+
And the output contains text:
572+
"""
573+
.before hook executed
574+
step has executed
575+
"""

cucumber-tsflow/src/binding-decorator.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import {
22
After,
33
AfterAll,
4+
AfterStep,
45
Before,
56
BeforeAll,
7+
BeforeStep,
68
Given,
79
Then,
810
When,
@@ -342,6 +344,12 @@ function bindHook(stepBinding: StepBinding): void {
342344
case StepBindingFlags.beforeAll:
343345
BeforeAll(globalBindFunc);
344346
break;
347+
case StepBindingFlags.beforeStep:
348+
BeforeStep(bindingFunc);
349+
break;
350+
case StepBindingFlags.afterStep:
351+
AfterStep(bindingFunc);
352+
break;
345353
case StepBindingFlags.afterAll:
346354
AfterAll(globalBindFunc);
347355
break;

cucumber-tsflow/src/hook-decorators.ts

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
import type {IDefineTestCaseHookOptions, IDefineTestRunHookOptions} from '@cucumber/cucumber/lib/support_code_library_builder/types';
1+
import {
2+
IDefineTestCaseHookOptions,
3+
IDefineTestRunHookOptions,
4+
IDefineTestStepHookOptions
5+
} from "@cucumber/cucumber/lib/support_code_library_builder/types";
26
import { BindingRegistry } from "./binding-registry";
37
import { Callsite } from "./our-callsite";
48
import { StepBinding, StepBindingFlags } from "./step-binding";
@@ -87,3 +91,23 @@ export function beforeAll(options?: IDefineTestRunHookOptions): MethodDecorator
8791
export function afterAll(options?: IDefineTestRunHookOptions): MethodDecorator {
8892
return createHookDecorator(StepBindingFlags.afterAll, options);
8993
}
94+
95+
/**
96+
* A method decorator that marks the associated function as a 'Before Step' step. The function is
97+
* executed before each step.
98+
*
99+
* @param options Optional hook options object.
100+
*/
101+
export function beforeStep(options?: IDefineTestStepHookOptions): MethodDecorator {
102+
return createHookDecorator(StepBindingFlags.beforeStep, options);
103+
}
104+
105+
/**
106+
* A method decorator that marks the associated function as an 'After Step' step. The function is
107+
* executed after each step.
108+
*
109+
* @param options Optional hook options object.
110+
*/
111+
export function afterStep(options?: IDefineTestStepHookOptions): MethodDecorator {
112+
return createHookDecorator(StepBindingFlags.afterStep, options);
113+
}

cucumber-tsflow/src/step-binding-flags.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,16 @@ export enum StepBindingFlags {
4343
*/
4444
afterAll = 1 << 6,
4545

46+
/**
47+
* A 'Before Step' hook binding.
48+
*/
49+
beforeStep = 1 << 7,
50+
51+
/**
52+
* An 'After Step' hook binding.
53+
*/
54+
afterStep = 1 << 8,
55+
4656
/**
4757
* All step definition bindings.
4858
*/
@@ -56,5 +66,7 @@ export enum StepBindingFlags {
5666
Hooks = StepBindingFlags.before |
5767
StepBindingFlags.after |
5868
StepBindingFlags.beforeAll |
59-
StepBindingFlags.afterAll,
69+
StepBindingFlags.afterAll |
70+
StepBindingFlags.beforeStep |
71+
StepBindingFlags.afterStep,
6072
}

0 commit comments

Comments
 (0)