diff --git a/content/docs/cucumber/api.md b/content/docs/cucumber/api.md index c01fb41d..b68ccedd 100644 --- a/content/docs/cucumber/api.md +++ b/content/docs/cucumber/api.md @@ -924,9 +924,11 @@ package. The `@CucumberOptions` can be used to provide [additional configuration](#list-configuration-options) to the runner. +**Using plugins:** For example if you want to tell Cucumber to use the two formatter plugins `pretty` and `html`, you can specify it like this: +{{% block "java" %}} ```java package com.example; @@ -939,7 +941,9 @@ import org.junit.runner.RunWith; public class RunCucumberTest { } ``` +{{% /block %}} +{{% block "kotlin" %}} ```kotlin package com.example; @@ -952,6 +956,44 @@ import org.junit.runner.RunWith; class RunCucumberTest { } ``` +{{% /block %}} + +For example if you want to tell Cucumber to print code snippets for missing +step definitions use the `summary` plugin, you can specify it like this: + +{{% block "java" %}} +```java +package com.example; + +import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +@CucumberOptions(plugin = {"pretty", "summary"}, strict = true, snippets = CAMELCASE) +public class RunCucumberTest { +} +``` +{{% /block %}} + +{{% block "kotlin" %}} +```kotlin +package com.example; + +import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +@CucumberOptions(plugin = {"pretty", "summary"}, strict = true, snippets = CAMELCASE) +class RunCucumberTest { +} +``` +{{% /block %}} +The default option for `snippets` is `UNDERSCORE`. This settings can be used to +specify the way code snippets will be created by Cucumber. + +**Performing a dry-run:** For example if you want to check whether all feature file steps have corresponding step definitions, you can specify it like this: @@ -982,6 +1024,8 @@ class RunCucumberTest { ``` The default option for `dryRun` is `false`. +**Formatting console output:** + For example if you want console output from Cucumber in a readable format, you can specify it like this: ```java @@ -1012,6 +1056,8 @@ class RunCucumberTest { The default option for `monochrome` is `false`. +**Skip undefined tests:** + For example if you want to skip undefined steps from execution, you can specify it like this: ```java @@ -1041,8 +1087,9 @@ class RunCucumberTest { ``` The default option for `strict` is `false`. -For example if you want to tell Cucumber to print code snippets for missing -step definitions use the `summary` plugin, you can specify it like this: +**Select scenarios using tags:** + +For example if you want to tell Cucumber to only run the scenarios specified with specific tags, you can specify it like this: {{% block "java" %}} ```java @@ -1053,7 +1100,7 @@ import io.cucumber.junit.CucumberOptions; import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(plugin = {"pretty", "summary"}, strict = true) +@CucumberOptions(tags = {"@foo", "not @bar"}) public class RunCucumberTest { } ``` @@ -1068,13 +1115,51 @@ import io.cucumber.junit.CucumberOptions; import org.junit.runner.RunWith; @RunWith(Cucumber.class) -@CucumberOptions(plugin = {"pretty", "summary"}, strict = true) -class RunCucumberTest { +@CucumberOptions(tags = {"@foo", "not @bar"}) +public class RunCucumberTest { } ``` {{% /block %}} -Usually, this class will be empty. You can, however, specify several JUnit rules. +**Specify an object factory:** + +For example if you are using Cucumber with a DI framework and want to use a custom object factory, you can specify it like this: + +{{% block "java" %}} +```java +package com.example; + +import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +@CucumberOptions(objectFactory = FooFactory.class) +public class RunCucumberTest { +} +``` +{{% /block %}} + +{{% block "kotlin" %}} +```kotlin +package com.example; + +import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; +import org.junit.runner.RunWith; + +@RunWith(Cucumber.class) +@CucumberOptions(objectFactory = FooFactory.class) +public class RunCucumberTest { +} +``` +{{% /block %}} +The default option for `objectFactory` is to use the default object factory. +Additional information about using custom object factories can be found [here](/docs/cucumber/state/#the-cucumber-object-factory). + +There are additional options available in the `@CucumberOptions` annotation. + +Usually, the test class will be empty. You can, however, specify several JUnit rules. {{% note "Supported JUnit annotations"%}} Cucumber supports JUnits `@ClassRule`, `@BeforeClass` and `@AfterClass` annotations. diff --git a/content/docs/cucumber/state.md b/content/docs/cucumber/state.md index 5db9dc78..94966f09 100644 --- a/content/docs/cucumber/state.md +++ b/content/docs/cucumber/state.md @@ -1,6 +1,6 @@ --- title: State -subtitle: Sharing state, isolated state +subtitle: Sharing state, isolated state, dependency injection --- It's important to prevent state created by one scenario from leaking into others. @@ -238,6 +238,167 @@ compile group: 'io.cucumber', name: 'cucumber-needle', version: '{{% version "cu There is no documentation yet, but the code is on [GitHub](https://github.com/cucumber/cucumber-jvm/tree/master/needle). +# How to use DI + +When using a DI framework all your step definitions, hooks, transformers, etc. will be created by the frameworks instance injector. + +## The need for a custom injector + +Cucumber example tests are typically small and have no dependencies. +In real life, though, tests often need access to application specific object instances +which also need to be supplied by the injector. +These instances need to be made available to your step definitions so that actions +can be applied on them and delivered results can be tested. + +The reason using Cucumber with a DI framework typically originates from the fact that the tested application also uses +the same framework. So we need to configure a custom injector to be used with Cucumber. +This injector ties tests and application instances together. + +Here is an example of a typical step definition using [Google Guice](/docs/cucumber/state/#guice). Using the +Cucumber provided Guice injector will fail to instantiate the required `appService` member. + +```java +package com.example.app; + +import static org.junit.Assert.assertTrue; + +import io.cucumber.java.en.When; +import io.cucumber.java.en.Then; +import io.cucumber.guice.ScenarioScoped; +import com.example.app.service.AppService; +import java.util.Objects; +import javax.inject.Inject; + +@ScenarioScoped +public final class StepDefinition { + + private final AppService appService; + + @Inject + public StepDefinition( AppService appService ) { + this.appService = Objects.requireNonNull( appService, "appService must not be null" ); + } + + @When("the application services are started") + public void startServices() { + this.appService.startServices(); + } + + @Then("all application services should be running") + public void checkThatApplicationServicesAreRunning() { + assertTrue( this.appService.servicesAreRunning() ); + } +} +``` + +The implementation of the AppService may need further arguments and configuration that typically +has to be provided by a Guice module. Guice modules are used to configure an injector and might look like this: + +```java +package com.example.app.service.impl; + +import com.example.app.service.AppService; +import com.google.inject.AbstractModule; + +public final class ServiceModule extends AbstractModule { + @Override + protected void configure() { + bind( AppService.class ).to( AppServiceImpl.class ); + // ... (further bindings) + } +} +``` + +The actual injector is then created like this: `injector = Guice.createInjector( new ServiceModule() );` + +This means we need to create our own injector and tell Cucumber to use it. + +## The Cucumber object factory + +Whenever Cucumber needs a specific object, it uses an object factory. +Cucumber has a default object factory that (in case of Guice) creates a default injector and +delegates object creation to that injector. +If you want to customize the injector we need to provide our own object factory and tell Cucumber to use it instead. + +```java +package com.example.app; + +import io.cucumber.core.backend.ObjectFactory; +import io.cucumber.guice.CucumberModules; +import io.cucumber.guice.ScenarioScope; +import com.example.app.service.impl.ServiceModule; +import com.google.inject.Guice; +import com.google.inject.Injector; +import com.google.inject.Stage; + +public final class CustomObjectFactory implements ObjectFactory { + + private Injector injector; + + public CustomObjectFactory() { + // Create an injector with our service module + this.injector = + Guice.createInjector( Stage.PRODUCTION, CucumberModules.createScenarioModule(), new ServiceModule()); + } + + @Override + public boolean addClass( Class< ? > clazz ) { + return true; + } + + @Override + public void start() { + this.injector.getInstance( ScenarioScope.class ).enterScope(); + } + + @Override + public void stop() { + this.injector.getInstance( ScenarioScope.class ).exitScope(); + } + + @Override + public < T > T getInstance( Class< T > clazz ) { + return this.injector.getInstance( clazz ); + } +} +``` + +This is the default object factory for Guice except that we have added our own bindings to the injector. +Cucumber loads the object factory through the `java.util.ServiceLoader`. In order for the ServiceLoader to be able +to pick up our custom implementation we need to provide the file `META-INF/services/io.cucumber.core.backend.ObjectFactory`. + +``` +com.example.app.CustomObjectFactory +# +# ... additional custom object factories could be added here +# +``` + +Now we have to tell Cucumber to use our custom object factory. There are several ways how this could be accomplished. + +### Using the command line + +When Cucumber is run from the command line, the custom object factory can be specified as argument. + +```bash +java io.cucumber.core.cli.Main --object-factory com.example.app.CustomObjectFactory +``` + +### Using the property file + +Cucumber makes use of a properties file (`cucumber.properties`) if it exists. The custom object factory can be +specified in this file and will be picked up when Cucumber is running. The following entry needs to be available +in the `cucumber.properties` file: + +``` +cucumber.object-factory=com.example.app.CustomObjectFactory +``` + +### Using a test runner (JUnit/TestNG) + +The Cucumber modules for [JUnit](/docs/cucumber/api/#junit) and [TestNG](/docs/cucumber/checking-assertions/#testng) allow to run Cucumber through a JUnit/TestNG test. +The custom object factory can be configured using the `@CucumberOptions` annotation. + # Databases There are several options to remove state from your database, to prevent leaking state between scenarios.