Skip to content

Commit 54ecea4

Browse files
committed
Raise an exception when loading glue code if hooks are not properly defined
And use immutable types with var rather than mutable types with val (ScalaDslRegistry)
1 parent bd67823 commit 54ecea4

File tree

10 files changed

+325
-48
lines changed

10 files changed

+325
-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)