Skip to content

Commit 9ec2f94

Browse files
authored
Merge pull request #62 from cucumber/error-if-hooks-undefined
Raise exceptions on incorrect hook definition
2 parents e4dc3e6 + 4f8c823 commit 9ec2f94

File tree

10 files changed

+326
-48
lines changed

10 files changed

+326
-48
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ See also the [CHANGELOG](https://github.com/cucumber/cucumber-jvm/blob/master/CH
1919

2020
### Fixed
2121

22+
- [Scala DSL] Raise an exception at runtime if hooks are not correctly defined ([#60](https://github.com/cucumber/cucumber-jvm-scala/issues/60) Gaël Jourdan-Weil)
23+
2224
## [5.7.0] (2020-05-10)
2325

2426
### Added

docs/hooks.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ Scenario hooks run for every scenario.
1616
```scala
1717
Before { scenario : Scenario =>
1818
// Do something before each scenario
19+
// Must return Unit
1920
}
2021

2122
// Or:
2223
Before {
2324
// Do something before each scenario
25+
// Must return Unit
2426
}
2527
```
2628

@@ -31,11 +33,13 @@ Before {
3133
```scala
3234
After { scenario : Scenario =>
3335
// Do something after each scenario
36+
// Must return Unit
3437
}
3538

3639
// Or:
3740
After {
3841
// Do something after each scenario
42+
// Must return Unit
3943
}
4044
```
4145

@@ -48,11 +52,13 @@ Step hooks invoked before and after a step.
4852
```scala
4953
BeforeStep { scenario : Scenario =>
5054
// Do something before step
55+
// Must return Unit
5156
}
5257

5358
// Or:
5459
BeforeStep {
5560
// Do something before step
61+
// Must return Unit
5662
}
5763
```
5864

@@ -61,11 +67,13 @@ BeforeStep {
6167
```scala
6268
AfterStep { scenario : Scenario =>
6369
// Do something after step
70+
// Must return Unit
6471
}
6572

6673
// Or:
6774
AfterStep {
6875
// Do something after step
76+
// Must return Unit
6977
}
7078
```
7179

@@ -76,6 +84,7 @@ Hooks can be conditionally selected for execution based on the tags of the scena
7684
```scala
7785
Before("@browser and not @headless") {
7886
// Do something before each scenario with tag @browser but not @headless
87+
// Must return Unit
7988
}
8089
```
8190

@@ -86,10 +95,12 @@ You can define an order between multiple hooks.
8695
```scala
8796
Before(10) {
8897
// Do something before each scenario
98+
// Must return Unit
8999
}
90100

91101
Before(20) {
92102
// Do something before each scenario
103+
// Must return Unit
93104
}
94105
```
95106

@@ -101,5 +112,6 @@ You mix up conditional and order hooks with following syntax:
101112
```scala
102113
Before("@browser and not @headless", 10) {
103114
// Do something before each scenario
115+
// Must return Unit
104116
}
105117
```

scala/sources/src/main/scala/io/cucumber/scala/GlueAdaptor.scala

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,17 @@ class GlueAdaptor(glue: Glue) {
1111
* @param scenarioScoped true for class instances, false for object singletons
1212
*/
1313
def loadRegistry(registry: ScalaDslRegistry, scenarioScoped: Boolean): Unit = {
14+
15+
// If the registry is not consistent, this indicates a mistake in the users definition and we want to let him know.
16+
registry.checkConsistency().left.foreach { ex: IncorrectHookDefinitionException =>
17+
throw ex
18+
}
19+
1420
registry.stepDefinitions.map(ScalaStepDefinition(_, scenarioScoped)).foreach(glue.addStepDefinition)
1521
registry.beforeHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addBeforeHook)
1622
registry.afterHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addAfterHook)
17-
registry.afterStepHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addAfterStepHook)
1823
registry.beforeStepHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addBeforeStepHook)
24+
registry.afterStepHooks.map(ScalaHookDefinition(_, scenarioScoped)).foreach(glue.addAfterStepHook)
1925
registry.docStringTypes.map(ScalaDocStringTypeDefinition(_, scenarioScoped)).foreach(glue.addDocStringType)
2026
registry.dataTableTypes.map(ScalaDataTableTypeDefinition(_, scenarioScoped)).foreach(glue.addDataTableType)
2127
registry.parameterTypes.map(ScalaParameterTypeDefinition(_, scenarioScoped)).foreach(glue.addParameterType)
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package io.cucumber.scala
2+
3+
import io.cucumber.core.backend.CucumberBackendException
4+
5+
object IncorrectHookDefinitionException {
6+
7+
def errorMessage(expectedHooks: Seq[UndefinedHook]): String = {
8+
val hooksListToDisplay = expectedHooks.map { eh =>
9+
s" - ${eh.stackTraceElement.getFileName}:${eh.stackTraceElement.getLineNumber} (${eh.hookType})"
10+
}
11+
12+
s"""Some hooks are not defined properly:
13+
|${hooksListToDisplay.mkString("\n")}
14+
|
15+
|This can be caused by defining hooks where the body returns a Int or String rather than Unit.
16+
|
17+
|For instance, the following code:
18+
|
19+
| Before {
20+
| someInitMethodReturningInt()
21+
| }
22+
|
23+
|Should be replaced with:
24+
|
25+
| Before {
26+
| someInitMethodReturningInt()
27+
| ()
28+
| }
29+
|""".stripMargin
30+
}
31+
32+
}
33+
34+
class IncorrectHookDefinitionException(val undefinedHooks: Seq[UndefinedHook]) extends CucumberBackendException(IncorrectHookDefinitionException.errorMessage(undefinedHooks)) {
35+
36+
}
37+
38+
case class UndefinedHook(hookType: HookType, stackTraceElement: StackTraceElement)

scala/sources/src/main/scala/io/cucumber/scala/ScalaDsl.scala

Lines changed: 29 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,22 @@ trait ScalaDsl extends BaseScalaDsl with StepDsl with HookDsl with DataTableType
2222

2323
}
2424

25-
private[scala] trait HookDsl extends BaseScalaDsl {
25+
sealed trait HookType
2626

27-
private sealed trait HookType
27+
object HookType {
2828

29-
private object HookType {
29+
case object BEFORE extends HookType
3030

31-
case object BEFORE extends HookType
31+
case object BEFORE_STEP extends HookType
3232

33-
case object BEFORE_STEP extends HookType
33+
case object AFTER extends HookType
3434

35-
case object AFTER extends HookType
35+
case object AFTER_STEP extends HookType
3636

37-
case object AFTER_STEP extends HookType
37+
}
3838

39-
}
39+
private[scala] trait HookDsl extends BaseScalaDsl {
40+
self =>
4041

4142
/**
4243
* Defines an before hook.
@@ -63,7 +64,7 @@ private[scala] trait HookDsl extends BaseScalaDsl {
6364
* @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed
6465
* @param order the order in which this hook should run. Higher numbers are run first
6566
*/
66-
def Before(tagExpression: String, order: Int) = new HookBody(HookType.BEFORE, tagExpression, order)
67+
def Before(tagExpression: String, order: Int) = new HookBody(HookType.BEFORE, tagExpression, order, Utils.frame(self))
6768

6869
/**
6970
* Defines an before step hook.
@@ -90,7 +91,7 @@ private[scala] trait HookDsl extends BaseScalaDsl {
9091
* @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed
9192
* @param order the order in which this hook should run. Higher numbers are run first
9293
*/
93-
def BeforeStep(tagExpression: String, order: Int) = new HookBody(HookType.BEFORE_STEP, tagExpression, order)
94+
def BeforeStep(tagExpression: String, order: Int) = new HookBody(HookType.BEFORE_STEP, tagExpression, order, Utils.frame(self))
9495

9596
/**
9697
* Defines and after hook.
@@ -117,7 +118,7 @@ private[scala] trait HookDsl extends BaseScalaDsl {
117118
* @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed
118119
* @param order the order in which this hook should run. Higher numbers are run first
119120
*/
120-
def After(tagExpression: String, order: Int) = new HookBody(HookType.AFTER, tagExpression, order)
121+
def After(tagExpression: String, order: Int) = new HookBody(HookType.AFTER, tagExpression, order, Utils.frame(self))
121122

122123
/**
123124
* Defines and after step hook.
@@ -144,26 +145,21 @@ private[scala] trait HookDsl extends BaseScalaDsl {
144145
* @param tagExpression a tag expression, if the expression applies to the current scenario this hook will be executed
145146
* @param order the order in which this hook should run. Higher numbers are run first
146147
*/
147-
def AfterStep(tagExpression: String, order: Int) = new HookBody(HookType.AFTER_STEP, tagExpression, order)
148+
def AfterStep(tagExpression: String, order: Int) = new HookBody(HookType.AFTER_STEP, tagExpression, order, Utils.frame(self))
149+
150+
final class HookBody(hookType: HookType, tagExpression: String, order: Int, frame: StackTraceElement) {
148151

149-
final class HookBody(hookType: HookType, tagExpression: String, order: Int) {
152+
// When a HookBody is created, we want to ensure that the apply method is called
153+
// To be able to check this, we notice the registry to expect a hook
154+
registry.expectHook(hookType, frame)
150155

151156
def apply(body: => Unit): Unit = {
152157
apply(_ => body)
153158
}
154159

155160
def apply(body: Scenario => Unit): Unit = {
156161
val details = ScalaHookDetails(tagExpression, order, body)
157-
hookType match {
158-
case HookType.BEFORE =>
159-
registry.beforeHooks += details
160-
case HookType.BEFORE_STEP =>
161-
registry.beforeStepHooks += details
162-
case HookType.AFTER =>
163-
registry.afterHooks += details
164-
case HookType.AFTER_STEP =>
165-
registry.afterStepHooks += details
166-
}
162+
registry.registerHook(hookType, details, frame)
167163
}
168164

169165
}
@@ -181,7 +177,7 @@ private[scala] trait DocStringTypeDsl extends BaseScalaDsl {
181177
* @tparam T type to convert to
182178
*/
183179
def DocStringType[T](contentType: String)(body: DocStringDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
184-
registry.docStringTypes += ScalaDocStringTypeDetails[T](contentType, body, ev)
180+
registry.registerDocStringType(ScalaDocStringTypeDetails[T](contentType, body, ev))
185181
}
186182

187183
}
@@ -209,19 +205,19 @@ private[scala] trait DataTableTypeDsl extends BaseScalaDsl {
209205
final class DataTableTypeBody(replaceWithEmptyString: Seq[String]) {
210206

211207
def apply[T](body: DataTableEntryDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
212-
registry.dataTableTypes += ScalaDataTableEntryTypeDetails[T](replaceWithEmptyString, body, ev)
208+
registry.registerDataTableType(ScalaDataTableEntryTypeDetails[T](replaceWithEmptyString, body, ev))
213209
}
214210

215211
def apply[T](body: DataTableRowDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
216-
registry.dataTableTypes += ScalaDataTableRowTypeDetails[T](replaceWithEmptyString, body, ev)
212+
registry.registerDataTableType(ScalaDataTableRowTypeDetails[T](replaceWithEmptyString, body, ev))
217213
}
218214

219215
def apply[T](body: DataTableCellDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
220-
registry.dataTableTypes += ScalaDataTableCellTypeDetails[T](replaceWithEmptyString, body, ev)
216+
registry.registerDataTableType(ScalaDataTableCellTypeDetails[T](replaceWithEmptyString, body, ev))
221217
}
222218

223219
def apply[T](body: DataTableDefinitionBody[T])(implicit ev: ClassTag[T]): Unit = {
224-
registry.dataTableTypes += ScalaDataTableTableTypeDetails[T](replaceWithEmptyString, body, ev)
220+
registry.registerDataTableType(ScalaDataTableTableTypeDetails[T](replaceWithEmptyString, body, ev))
225221
}
226222

227223
}
@@ -398,7 +394,7 @@ private[scala] trait ParameterTypeDsl extends BaseScalaDsl {
398394
}
399395

400396
private def register[R](pf: PartialFunction[List[String], R])(implicit tag: ClassTag[R]): Unit = {
401-
registry.parameterTypes += ScalaParameterTypeDetails[R](name, regex, pf, tag)
397+
registry.registerParameterType(ScalaParameterTypeDetails[R](name, regex, pf, tag))
402398
}
403399

404400
}
@@ -413,7 +409,7 @@ private[scala] trait DefaultTransformerDsl extends BaseScalaDsl {
413409
* @param body converts `String` argument to an instance of the `Type` argument
414410
*/
415411
def DefaultParameterTransformer(body: DefaultParameterTransformerBody): Unit = {
416-
registry.defaultParameterTransformers += ScalaDefaultParameterTransformerDetails(body)
412+
registry.registerDefaultParameterTransformer(ScalaDefaultParameterTransformerDetails(body))
417413
}
418414

419415
/**
@@ -441,7 +437,7 @@ private[scala] trait DefaultTransformerDsl extends BaseScalaDsl {
441437
}
442438

443439
private def DefaultDataTableCellTransformer(replaceWithEmptyString: Seq[String])(body: DefaultDataTableCellTransformerBody): Unit = {
444-
registry.defaultDataTableCellTransformers += ScalaDefaultDataTableCellTransformerDetails(replaceWithEmptyString, body)
440+
registry.registerDefaultDataTableCellTransformer(ScalaDefaultDataTableCellTransformerDetails(replaceWithEmptyString, body))
445441
}
446442

447443
/**
@@ -468,7 +464,7 @@ private[scala] trait DefaultTransformerDsl extends BaseScalaDsl {
468464
}
469465

470466
private def DefaultDataTableEntryTransformer(replaceWithEmptyString: Seq[String])(body: DefaultDataTableEntryTransformerBody): Unit = {
471-
registry.defaultDataTableEntryTransformers += ScalaDefaultDataTableEntryTransformerDetails(replaceWithEmptyString, body)
467+
registry.registerDefaultDataTableEntryTransformer(ScalaDefaultDataTableEntryTransformerDetails(replaceWithEmptyString, body))
472468
}
473469

474470
}
@@ -911,7 +907,7 @@ private[scala] trait StepDsl extends BaseScalaDsl {
911907
}
912908

913909
private def register(manifests: Manifest[_ <: Any]*)(pf: PartialFunction[List[Any], Any]): Unit = {
914-
registry.stepDefinitions += ScalaStepDetails(Utils.frame(self), name, regex, manifests, pf)
910+
registry.registerStep(ScalaStepDetails(Utils.frame(self), name, regex, manifests, pf))
915911
}
916912

917913
}

0 commit comments

Comments
 (0)