Skip to content

Commit 62908a1

Browse files
committed
feat: 🚀 Implement BeforeAll/AfterAll hooks
1 parent 03cc303 commit 62908a1

21 files changed

+536
-74
lines changed

‎CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ Check out the [Upgrade Guide](docs/upgrade_v8.md).
1313

1414
### Added
1515

16+
- [Scala] Added `BeforeAll` and `AfterAll` hooks. See [Hooks](docs/hooks.md).
17+
1618
### Changed
1719

1820
- [Core] Updated `cucumber-core` dependency to [7.0.0-RC1](https://github.com/cucumber/cucumber-jvm/blob/main/CHANGELOG.md)

‎cucumber-scala/src/main/scala/io/cucumber/scala/Aliases.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ package io.cucumber.scala
44
*/
55
object Aliases {
66

7+
type StaticHookDefinitionBody = () => Unit
8+
79
type HookDefinitionBody = Scenario => Unit
810

911
type StepDefinitionBody = () => Unit

‎cucumber-scala/src/main/scala/io/cucumber/scala/GlueAdaptor.scala

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,25 @@ class GlueAdaptor(glue: Glue) {
1515
): Unit = {
1616

1717
// If the registry is not consistent, this indicates a mistake in the users definition and we want to let him know.
18-
registry.checkConsistency().left.foreach {
18+
registry.checkConsistency(scenarioScoped).left.foreach {
1919
(ex: IncorrectHookDefinitionException) =>
2020
throw ex
2121
}
2222

2323
registry.stepDefinitions
2424
.map(ScalaStepDefinition(_, scenarioScoped))
2525
.foreach(glue.addStepDefinition)
26+
27+
// The presence of beforeAll/afterAll hooks with scenarioScoped is checked by checkConsistency above
28+
if (!scenarioScoped) {
29+
registry.beforeAllHooks
30+
.map(ScalaStaticHookDefinition(_))
31+
.foreach(glue.addBeforeAllHook)
32+
registry.afterAllHooks
33+
.map(ScalaStaticHookDefinition(_))
34+
.foreach(glue.addAfterAllHook)
35+
}
36+
2637
registry.beforeHooks
2738
.map(ScalaHookDefinition(_, scenarioScoped))
2839
.foreach(glue.addBeforeHook)
@@ -35,6 +46,7 @@ class GlueAdaptor(glue: Glue) {
3546
registry.afterStepHooks
3647
.map(ScalaHookDefinition(_, scenarioScoped))
3748
.foreach(glue.addAfterStepHook)
49+
3850
registry.docStringTypes
3951
.map(ScalaDocStringTypeDefinition(_, scenarioScoped))
4052
.foreach(glue.addDocStringType)
@@ -44,6 +56,7 @@ class GlueAdaptor(glue: Glue) {
4456
registry.parameterTypes
4557
.map(ScalaParameterTypeDefinition(_, scenarioScoped))
4658
.foreach(glue.addParameterType)
59+
4760
registry.defaultParameterTransformers
4861
.map(ScalaDefaultParameterTransformerDefinition(_, scenarioScoped))
4962
.foreach(glue.addDefaultParameterTransformer)

‎cucumber-scala/src/main/scala/io/cucumber/scala/HookDsl.scala

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,19 @@ package io.cucumber.scala
33
private[scala] trait HookDsl extends BaseScalaDsl {
44
self =>
55

6+
/** Defines a before all hook.
7+
*/
8+
def BeforeAll: StaticHookBody = BeforeAll(DEFAULT_AFTER_ORDER)
9+
10+
/** Defines a before all hook.
11+
* @param order the order in which this hook should run. Higher numbers are run first
12+
*/
13+
def BeforeAll(order: Int): StaticHookBody = new StaticHookBody(
14+
StaticHookType.BEFORE_ALL,
15+
order,
16+
Utils.frame(self)
17+
)
18+
619
/** Defines an before hook.
720
*/
821
def Before: HookBody = Before(EMPTY_TAG_EXPRESSION, DEFAULT_BEFORE_ORDER)
@@ -26,7 +39,12 @@ private[scala] trait HookDsl extends BaseScalaDsl {
2639
* @param order the order in which this hook should run. Higher numbers are run first
2740
*/
2841
def Before(tagExpression: String, order: Int) =
29-
new HookBody(HookType.BEFORE, tagExpression, order, Utils.frame(self))
42+
new HookBody(
43+
ScopedHookType.BEFORE,
44+
tagExpression,
45+
order,
46+
Utils.frame(self)
47+
)
3048

3149
/** Defines an before step hook.
3250
*/
@@ -52,7 +70,22 @@ private[scala] trait HookDsl extends BaseScalaDsl {
5270
* @param order the order in which this hook should run. Higher numbers are run first
5371
*/
5472
def BeforeStep(tagExpression: String, order: Int) =
55-
new HookBody(HookType.BEFORE_STEP, tagExpression, order, Utils.frame(self))
73+
new HookBody(
74+
ScopedHookType.BEFORE_STEP,
75+
tagExpression,
76+
order,
77+
Utils.frame(self)
78+
)
79+
80+
/** Defines a after all hook.
81+
*/
82+
def AfterAll: StaticHookBody = AfterAll(DEFAULT_AFTER_ORDER)
83+
84+
/** Defines a after all hook.
85+
* @param order the order in which this hook should run. Higher numbers are run first
86+
*/
87+
def AfterAll(order: Int): StaticHookBody =
88+
new StaticHookBody(StaticHookType.AFTER_ALL, order, Utils.frame(self))
5689

5790
/** Defines and after hook.
5891
*/
@@ -77,7 +110,7 @@ private[scala] trait HookDsl extends BaseScalaDsl {
77110
* @param order the order in which this hook should run. Higher numbers are run first
78111
*/
79112
def After(tagExpression: String, order: Int) =
80-
new HookBody(HookType.AFTER, tagExpression, order, Utils.frame(self))
113+
new HookBody(ScopedHookType.AFTER, tagExpression, order, Utils.frame(self))
81114

82115
/** Defines and after step hook.
83116
*/
@@ -102,10 +135,15 @@ private[scala] trait HookDsl extends BaseScalaDsl {
102135
* @param order the order in which this hook should run. Higher numbers are run first
103136
*/
104137
def AfterStep(tagExpression: String, order: Int) =
105-
new HookBody(HookType.AFTER_STEP, tagExpression, order, Utils.frame(self))
138+
new HookBody(
139+
ScopedHookType.AFTER_STEP,
140+
tagExpression,
141+
order,
142+
Utils.frame(self)
143+
)
106144

107145
final class HookBody(
108-
hookType: HookType,
146+
hookType: ScopedHookType,
109147
tagExpression: String,
110148
order: Int,
111149
frame: StackTraceElement
@@ -120,8 +158,25 @@ private[scala] trait HookDsl extends BaseScalaDsl {
120158
}
121159

122160
def apply(body: Scenario => Unit): Unit = {
123-
val details = ScalaHookDetails(tagExpression, order, body)
124-
registry.registerHook(hookType, details, frame)
161+
val details = ScalaHookDetails(tagExpression, order, body, frame)
162+
registry.registerDynamicHook(hookType, details)
163+
}
164+
165+
}
166+
167+
final class StaticHookBody(
168+
hookType: StaticHookType,
169+
order: Int,
170+
frame: StackTraceElement
171+
) {
172+
173+
// When a HookBody is created, we want to ensure that the apply method is called
174+
// To be able to check this, we notice the registry to expect a hook
175+
registry.expectHook(hookType, frame)
176+
177+
def apply(body: => Unit): Unit = {
178+
val details = ScalaStaticHookDetails(order, () => body, frame)
179+
registry.registerStaticHook(hookType, details)
125180
}
126181

127182
}

‎cucumber-scala/src/main/scala/io/cucumber/scala/HookType.scala

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,26 @@ package io.cucumber.scala
22

33
sealed trait HookType
44

5-
object HookType {
5+
sealed trait ScopedHookType extends HookType
66

7-
case object BEFORE extends HookType
7+
object ScopedHookType {
88

9-
case object BEFORE_STEP extends HookType
9+
case object BEFORE extends ScopedHookType
1010

11-
case object AFTER extends HookType
11+
case object BEFORE_STEP extends ScopedHookType
1212

13-
case object AFTER_STEP extends HookType
13+
case object AFTER extends ScopedHookType
14+
15+
case object AFTER_STEP extends ScopedHookType
16+
17+
}
18+
19+
sealed trait StaticHookType extends HookType
20+
21+
object StaticHookType {
22+
23+
case object BEFORE_ALL extends StaticHookType
24+
25+
case object AFTER_ALL extends StaticHookType
1426

1527
}

‎cucumber-scala/src/main/scala/io/cucumber/scala/IncorrectHookDefinitionException.scala

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@ package io.cucumber.scala
22

33
import io.cucumber.core.backend.CucumberBackendException
44

5+
sealed abstract class IncorrectHookDefinitionException(message: String)
6+
extends CucumberBackendException(message)
7+
58
object IncorrectHookDefinitionException {
69

7-
def errorMessage(expectedHooks: Seq[UndefinedHook]): String = {
10+
def undefinedHooksErrorMessage(expectedHooks: Seq[UndefinedHook]): String = {
811
val hooksListToDisplay = expectedHooks.map { eh =>
912
s" - ${eh.stackTraceElement.getFileName}:${eh.stackTraceElement.getLineNumber} (${eh.hookType})"
1013
}
@@ -29,11 +32,36 @@ object IncorrectHookDefinitionException {
2932
|""".stripMargin
3033
}
3134

35+
def scenarioScopedStaticHookErrorMessage(
36+
staticHooks: Seq[ScalaStaticHookDetails]
37+
): String = {
38+
val hooksListToDisplay: Seq[String] = staticHooks.map { h =>
39+
s" - ${h.stackTraceElement.getFileName}:${h.stackTraceElement.getLineNumber}"
40+
}
41+
42+
s"""Some hooks are not defined properly:
43+
|${hooksListToDisplay.mkString("\n")}
44+
|
45+
|This can be caused by defining static hooks (BeforeAll/AfterAll) in a class rather than in a object.
46+
|Such hooks can only be defined in a static context.
47+
|""".stripMargin
48+
}
49+
3250
}
3351

34-
class IncorrectHookDefinitionException(val undefinedHooks: Seq[UndefinedHook])
35-
extends CucumberBackendException(
36-
IncorrectHookDefinitionException.errorMessage(undefinedHooks)
52+
class UndefinedHooksException(val undefinedHooks: Seq[UndefinedHook])
53+
extends IncorrectHookDefinitionException(
54+
IncorrectHookDefinitionException.undefinedHooksErrorMessage(
55+
undefinedHooks
56+
)
57+
) {}
58+
59+
class ScenarioScopedStaticHookException(
60+
val staticHooks: Seq[ScalaStaticHookDetails]
61+
) extends IncorrectHookDefinitionException(
62+
IncorrectHookDefinitionException.scenarioScopedStaticHookErrorMessage(
63+
staticHooks
64+
)
3765
) {}
3866

3967
case class UndefinedHook(

0 commit comments

Comments
 (0)