From 485fa959f496eca61f6186a487d76d7986081862 Mon Sep 17 00:00:00 2001 From: Ralph Kar Date: Fri, 16 Aug 2019 11:05:46 +0200 Subject: [PATCH 1/6] State document updated with details on using DI --- content/docs/cucumber/state.md | 207 ++++++++++++++++++++++++++++++++- 1 file changed, 206 insertions(+), 1 deletion(-) diff --git a/content/docs/cucumber/state.md b/content/docs/cucumber/state.md index 5db9dc78..a8a94dea 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, details on DI --- It's important to prevent state created by one scenario from leaking into others. @@ -238,6 +238,211 @@ 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). +# Details of using DI (e.g. using Guice) + +(since Cucumber 5) + +It is typically very simple to use Cucumber with a dependency injection framework. +All that's needed is to add the appropriate dependency to the classpath. + +For example, if you want to use [Google Guice](https://github.com/google/guice) as DI +simply add the `cucumber-guice` dependency to your pom.xml: + +```xml + + [...] + + io.cucumber + cucumber-guice + ${cucumber.version} + test + + [...] + +``` + +Now all your step definitions, hooks, transformers, etc. will be created and supplied by a Guice injector. +This is pretty cool, but depending on your application it is far off from being sufficient. + +## The need for a custom injector + +Even though example tests are very simple, they often do not stay that simple when it comes +to large applications contexts. An application often has specific components (data managers, providers, +services, etc.). These components 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 Guice DI typically originates from the fact that the tested application also uses +Guice as DI framework. So why not bringing this together and provide a custom injector that can inject +the application components into the step definitions. + +```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() ); + } +} +``` + +This might not be working as expected. Why so? + +In order for Guice to create the step definition, an instance of AppService is needed as argument for the constructor. +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 have access to the creation of the injector that Cucumber creates in order to customize +it with our application specific modules. + +## The Cucumber object factory + +Whenever Cucumber needs a specific object, it asks an object factory for it. +Cucumber has a default object factory that (in case of Guice) creates a default injector and +delegates object creation to the injector. +In case we want to customize this injector we need to provide our own object factory and tell Cucumber to use it. + +```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 almost 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 +# +``` + +Our step definition should now be working except that we have to tell Cucumber to use our custom object factory. +There are several ways how this could be accomplished. + +### The command line + +When Cucumber is run from the command line, the custom object factory can be specified as class name argument. + +```bash +java io.cucumber.core.cli.Main --object-factory com.example.app.CustomObjectFactory +``` + +### The property file + +Cucumber makes use of a properties file (`cucumber.properties`) when 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 +``` + +### The test runner (JUnit/TestNG) + +The Cucumber modules for JUnit and TestNG allow to run Cucumber through a JUnit/TestNG test. When the test is +executed it actually starts the Cucumber engine that runs the Cucumber tests. Cucumber can be configured using +the `@CucumberOptions` annotation. The custom object factory can be specified in this annotation. + +```java +package com.example.app; + +import io.cucumber.junit.Cucumber; +import io.cucumber.junit.CucumberOptions; +import org.junit.runner.RunWith; + +@RunWith( Cucumber.class ) +@CucumberOptions( objectFactory = com.example.app.CustomObjectFactory.class ) +public final class RunCucumberTest { +} +``` + +Using the `@CucumberOptions` annotation has another important advantage. It is possible to create different tests that +run different Cucumber scenarios (using tags). In case certain scenarios need a differently configured injector, +several custom object factories may exist and can be referenced by different tests. +This is e.g. very helpful when certain aspects of an application need to be tested with different configurations +or in different runtime environments. + # Databases There are several options to remove state from your database, to prevent leaking state between scenarios. From 1726e1e837b3c968c6e8fd88e83f1239740786d5 Mon Sep 17 00:00:00 2001 From: Ralph Kar Date: Mon, 16 Sep 2019 14:24:19 +0200 Subject: [PATCH 2/6] Enhanced conciseness and readability --- content/docs/cucumber/state.md | 78 ++++++++++++---------------------- 1 file changed, 28 insertions(+), 50 deletions(-) diff --git a/content/docs/cucumber/state.md b/content/docs/cucumber/state.md index a8a94dea..6378e539 100644 --- a/content/docs/cucumber/state.md +++ b/content/docs/cucumber/state.md @@ -238,42 +238,26 @@ 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). -# Details of using DI (e.g. using Guice) +# Details of using DI (since Cucumber 5) -It is typically very simple to use Cucumber with a dependency injection framework. -All that's needed is to add the appropriate dependency to the classpath. - -For example, if you want to use [Google Guice](https://github.com/google/guice) as DI -simply add the `cucumber-guice` dependency to your pom.xml: - -```xml - - [...] - - io.cucumber - cucumber-guice - ${cucumber.version} - test - - [...] - -``` - -Now all your step definitions, hooks, transformers, etc. will be created and supplied by a Guice injector. -This is pretty cool, but depending on your application it is far off from being sufficient. +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 -Even though example tests are very simple, they often do not stay that simple when it comes -to large applications contexts. An application often has specific components (data managers, providers, -services, etc.). These components need to be made available to your step definitions so that actions +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 Guice DI typically originates from the fact that the tested application also uses -Guice as DI framework. So why not bringing this together and provide a custom injector that can inject -the application components into the step definitions. +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 injectors 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; @@ -309,9 +293,6 @@ public final class StepDefinition { } ``` -This might not be working as expected. Why so? - -In order for Guice to create the step definition, an instance of AppService is needed as argument for the constructor. 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: @@ -332,15 +313,14 @@ public final class ServiceModule extends AbstractModule { The actual injector is then created like this: `injector = Guice.createInjector( new ServiceModule() );` -This means we need to have access to the creation of the injector that Cucumber creates in order to customize -it with our application specific modules. +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 asks an object factory for it. +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 the injector. -In case we want to customize this injector we need to provide our own object factory and tell Cucumber to use it. +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; @@ -385,8 +365,7 @@ public final class CustomObjectFactory implements ObjectFactory { } ``` -This is almost the default object factory for Guice except that we have added our own bindings to the injector. - +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`. @@ -397,20 +376,19 @@ com.example.app.CustomObjectFactory # ``` -Our step definition should now be working except that we have to tell Cucumber to use our custom object factory. -There are several ways how this could be accomplished. +Now we have to tell Cucumber to use our custom object factory. There are several ways how this could be accomplished. -### The command line +### Using the command line -When Cucumber is run from the command line, the custom object factory can be specified as class name argument. +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 ``` -### The property file +### Using the property file -Cucumber makes use of a properties file (`cucumber.properties`) when it exists. The custom object factory can be +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: @@ -418,11 +396,10 @@ in the `cucumber.properties` file: cucumber.object-factory=com.example.app.CustomObjectFactory ``` -### The test runner (JUnit/TestNG) +### Using a test runner (JUnit/TestNG) -The Cucumber modules for JUnit and TestNG allow to run Cucumber through a JUnit/TestNG test. When the test is -executed it actually starts the Cucumber engine that runs the Cucumber tests. Cucumber can be configured using -the `@CucumberOptions` annotation. The custom object factory can be specified in this annotation. +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. ```java package com.example.app; @@ -438,9 +415,10 @@ public final class RunCucumberTest { ``` Using the `@CucumberOptions` annotation has another important advantage. It is possible to create different tests that -run different Cucumber scenarios (using tags). In case certain scenarios need a differently configured injector, +run different Cucumber scenarios (using [tags](/docs/cucumber/api/#tag-expressions)). +In case certain scenarios need a differently configured injector, several custom object factories may exist and can be referenced by different tests. -This is e.g. very helpful when certain aspects of an application need to be tested with different configurations +This is very helpful when certain aspects of an application need to be tested with different configurations or in different runtime environments. # Databases From 03783117f6a3dd205bae2b875e6194c035c8d3fa Mon Sep 17 00:00:00 2001 From: Ralph Kar Date: Mon, 16 Sep 2019 14:30:44 +0200 Subject: [PATCH 3/6] Header changed --- content/docs/cucumber/state.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/content/docs/cucumber/state.md b/content/docs/cucumber/state.md index 6378e539..1e08d843 100644 --- a/content/docs/cucumber/state.md +++ b/content/docs/cucumber/state.md @@ -238,9 +238,7 @@ 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). -# Details of using DI - -(since Cucumber 5) +# How to use DI When using a DI framework all your step definitions, hooks, transformers, etc. will be created by the frameworks instance injector. From 614fe17aa26b8103f5953cf0df6f61588a412cda Mon Sep 17 00:00:00 2001 From: Ralph Kar Date: Mon, 16 Sep 2019 14:37:02 +0200 Subject: [PATCH 4/6] Typo fixed --- content/docs/cucumber/state.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/docs/cucumber/state.md b/content/docs/cucumber/state.md index 1e08d843..28b1ef90 100644 --- a/content/docs/cucumber/state.md +++ b/content/docs/cucumber/state.md @@ -252,7 +252,7 @@ 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 injectors ties tests and application instances together. +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. From 0c1ff425776192fbb112ad1dcd71698ea92bc0c2 Mon Sep 17 00:00:00 2001 From: Ralph Kar Date: Tue, 17 Sep 2019 14:05:42 +0200 Subject: [PATCH 5/6] Added more description to the @CucumberOption annotation --- content/docs/cucumber/api.md | 93 +++++++++++++++++++++++++++++++--- content/docs/cucumber/state.md | 24 +-------- 2 files changed, 88 insertions(+), 29 deletions(-) diff --git a/content/docs/cucumber/api.md b/content/docs/cucumber/api.md index c01fb41d..4371a0c2 100644 --- a/content/docs/cucumber/api.md +++ b/content/docs/cucumber/api.md @@ -924,9 +924,10 @@ 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 +940,9 @@ import org.junit.runner.RunWith; public class RunCucumberTest { } ``` +{{% /block %}} +{{% block "kotlin" %}} ```kotlin package com.example; @@ -952,7 +955,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: ```java @@ -982,6 +1022,7 @@ 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 +1053,7 @@ 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 +1083,8 @@ 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 +1095,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 +1110,50 @@ 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 28b1ef90..b5b02198 100644 --- a/content/docs/cucumber/state.md +++ b/content/docs/cucumber/state.md @@ -1,6 +1,6 @@ --- title: State -subtitle: Sharing state, isolated state, details on DI +subtitle: Sharing state, isolated state, dependency injection --- It's important to prevent state created by one scenario from leaking into others. @@ -397,27 +397,7 @@ 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. - -```java -package com.example.app; - -import io.cucumber.junit.Cucumber; -import io.cucumber.junit.CucumberOptions; -import org.junit.runner.RunWith; - -@RunWith( Cucumber.class ) -@CucumberOptions( objectFactory = com.example.app.CustomObjectFactory.class ) -public final class RunCucumberTest { -} -``` - -Using the `@CucumberOptions` annotation has another important advantage. It is possible to create different tests that -run different Cucumber scenarios (using [tags](/docs/cucumber/api/#tag-expressions)). -In case certain scenarios need a differently configured injector, -several custom object factories may exist and can be referenced by different tests. -This is very helpful when certain aspects of an application need to be tested with different configurations -or in different runtime environments. +The custom object factory can be configured using the [@CucumberOptions](/docs/cucumber/api/#specify-an-object-factory) annotation. # Databases From 4025aa80d41d9a26a0829fc56620882e369c1ee7 Mon Sep 17 00:00:00 2001 From: Ralph Kar Date: Tue, 17 Sep 2019 14:20:57 +0200 Subject: [PATCH 6/6] MD error fixed --- content/docs/cucumber/api.md | 18 ++++++++++++------ content/docs/cucumber/state.md | 2 +- 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/content/docs/cucumber/api.md b/content/docs/cucumber/api.md index 4371a0c2..b68ccedd 100644 --- a/content/docs/cucumber/api.md +++ b/content/docs/cucumber/api.md @@ -924,7 +924,8 @@ package. The `@CucumberOptions` can be used to provide [additional configuration](#list-configuration-options) to the runner. -#### Using plugins +**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" %}} @@ -992,7 +993,8 @@ class RunCucumberTest { 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 +**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: ```java @@ -1022,7 +1024,8 @@ class RunCucumberTest { ``` The default option for `dryRun` is `false`. -#### Formatting console output +**Formatting console output:** + For example if you want console output from Cucumber in a readable format, you can specify it like this: ```java @@ -1053,7 +1056,8 @@ class RunCucumberTest { The default option for `monochrome` is `false`. -#### Skip undefined tests +**Skip undefined tests:** + For example if you want to skip undefined steps from execution, you can specify it like this: ```java @@ -1083,7 +1087,8 @@ class RunCucumberTest { ``` The default option for `strict` is `false`. -#### Select scenarios using tags +**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" %}} @@ -1116,7 +1121,8 @@ public class RunCucumberTest { ``` {{% /block %}} -#### Specify an object factory +**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" %}} diff --git a/content/docs/cucumber/state.md b/content/docs/cucumber/state.md index b5b02198..94966f09 100644 --- a/content/docs/cucumber/state.md +++ b/content/docs/cucumber/state.md @@ -397,7 +397,7 @@ 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](/docs/cucumber/api/#specify-an-object-factory) annotation. +The custom object factory can be configured using the `@CucumberOptions` annotation. # Databases