Cucumber is a great framework for building test automation tools with [human-readable language](/www.jvt.me/posts/2021/08/24/human-readable-gherkin/) and I've used it a fair bit in previous years with Java projects.
One thing you will eventually start to feel is the need for a good dependency injection framework, otherwise your code will be littered with re-creating the same objects to use them, or using things like `ThreadLocal` or `static`s to share state, which isn't ideal.
At the time of writing, Cucumber (7.1.0) still recommends using PicoContainer as the default dependency injection framework, and it's been blogged about a fair bit before, such as the wonderful <spanclass="h-card"><aclass="u-url"href="https://angiejones.tech/">Angie Jones</a></span>' blog post [_Sharing State Between Cucumber Steps with Dependency Injection_](https://angiejones.tech/sharing-state-between-steps-in-cucumber-with-dependency-injection/).
However as [I've blogged about](/posts/2021/12/30/cucumber-picocontainer-gotcha/), PicoContainer isn't ideal because it requires zero-args constructors, which isn't always possible.
# Why Dagger?
although there are other dependency injection frameworks [called out in the Cucumber docs for _Sharing state between steps_](https://cucumber.io/docs/cucumber/state/), I instead recommend [Dagger](https://dagger.dev). As noted in [_Lightweight and Powerful Dependency Injection for JVM-based Applications with Dagger_](/posts/2021/10/19/dagger-java-lambda/), Dagger is a great, super lightweight framework that works really nicely, and is fairly easy to get set up in a project.
I'd very much recommend a read of that post for a bit more about why Dagger is great, and we'll discuss at the end of the post why you may not see it very often.
If you're comfortable with using Spring as your dependency injection framework, especially if using Spring Boot, I'd recommend continuing with it so we don't have to maintain two styles of dependency injection setup.
# Base setup
Example code can be found [on a branch on GitLab](https://gitlab.com/jamietanna/cucumber-dagger/).
Let us say that we're following a common practice of using constructor injection for objects, such as the below code snippet:
```java
packageio.cucumber.skeleton;
importio.cucumber.java.en.Given;
publicclassStepDefinitions{
privatefinalBellybelly;
publicStepDefinitions(Bellybelly){
this.belly=belly;
}
@Given("I have {int} cukes in my belly")
publicvoidI_have_cukes_in_my_belly(intcukes){
belly.eat(cukes);
}
}
```
And the following `Belly` class definition:
```java
packageio.cucumber.skeleton;
publicclassBelly{
publicvoideat(intcukes){
}
}
```
# Migrating to Dagger
To be able to migrate to Dagger, we'd follow a similar setup as mentioned in my previous Dagger article, which is to set up the dependencies (in this example for Gradle, when running Cucumber tests from `src/test/resources`):
Which we need to modify the `StepDefinitions`, to utilise the new Dagger-built configuration:
```diff
package io.cucumber.skeleton;
import io.cucumber.java.en.Given;
+import io.cucumber.skeleton.config.DaggerConfig;
public class StepDefinitions {
private final Belly belly;
- public StepDefinitions(Belly belly) {
- this.belly = belly;
+ public StepDefinitions() {
+ this.belly = DaggerConfig.create().belly();
}
@Given("I have {int} cukes in my belly")
```
## Allowing step definition classes to be unit testable
Notice that this has resulted in us using zero-args constructor in the step definitions - unless we're [unit testing our steps](/posts/2018/11/07/unit-test-functional-tests/) this will be fine, and if we are, we can provide a test-only constructor to inject in mock configuration:
```java
packageio.cucumber.skeleton;
importio.cucumber.java.en.Given;
importio.cucumber.skeleton.config.Config;
importio.cucumber.skeleton.config.DaggerConfig;
publicclassStepDefinitions{
privatefinalBellybelly;
publicStepDefinitions(){
this(DaggerConfig.create());
}
StepDefinitions(Configconfig){
this.belly=config.belly();
}
@Given("I have {int} cukes in my belly")
publicvoidI_have_cukes_in_my_belly(intcukes){
belly.eat(cukes);
}
}
```
## Configurability
As mentioned in [_Lightweight and Powerful Dependency Injection for JVM-based Applications with Dagger_](/posts/2021/10/19/dagger-java-lambda/#improved-handling-of-configuration), this can be improved by adding in a `Builder` that can then take environment specific configuration, allowing you to i.e. set up your tests to run differently against different environments.
# Caveats
Notice that there's no first-class support in Cucumber's dependency tree - this is because Dagger configuration is very personal to the project, so there's no out-of-the-box way to do it.
I'm going to look at working with others on the Cucumber team to see if there's a way we can do this, and even if it's not first-class support, there may be a way we can produce a dependency that makes it easier to utilise.
At the time of writing, Cucumber (7.1.0) still recommends using PicoContainer as the default dependency injection framework.
Although this works for a lot of cases that folks are building for, there is unfortunately a big gotcha that I've hit a few times, and have since avoided using PicoContainer due to this problem.
Let us say that we're following a common practice of using constructor injection for objects, such as the below code snippet:
```java
packageio.cucumber.skeleton;
importio.cucumber.java.en.Given;
publicclassStepDefinitions{
privatefinalBellybelly;
publicStepDefinitions(Bellybelly){
this.belly=belly;
}
@Given("I have {int} cukes in my belly")
publicvoidI_have_cukes_in_my_belly(intcukes){
belly.eat(cukes);
}
}
```
And the following `Belly` class definition:
```java
packageio.cucumber.skeleton;
publicclassBelly{
publicvoideat(intcukes){
}
}
```
(Example code can be found [on a branch on GitLab](https://gitlab.com/jamietanna/cucumber-dagger/-/blob/defect/picocontainer/src/test/java/io/cucumber/skeleton/StepDefinitions.java))
This works absolutely fine, as our `Belly` class can be instantiated with the implicit zero-args constructor that Java provides.
However, if we make our `Belly` class require some configuration via the constructor, such as:
```diff
package io.cucumber.skeleton;
public class Belly {
+
+ public Belly(int initial) {
+ // TODO: something with initial
+ }
+
public void eat(int cukes) {
}
}
```
We notice that running this leads to the following error when executing the tests:
```
RunCucumberTest > Cucumber > Belly > io.cucumber.skeleton.RunCucumberTest.Belly - a few cukes STANDARD_OUT
Scenario: a few cukes # io/cucumber/skeleton/belly.feature:3
Given I have 42 cukes in my belly # io.cucumber.skeleton.StepDefinitions.I_have_cukes_in_my_belly(int)
org.picocontainer.injectors.AbstractInjector$UnsatisfiableDependenciesException: io.cucumber.skeleton.Belly has
unsatisfied dependency 'class java.lang.Integer' for constructor
'public io.cucumber.skeleton.Belly(int)' from org.picocontainer.DefaultPicoContainer@2f05be7f:2<|
```
Unfortunately, PicoContainer's reliance on a zero-args constructor has been broken, which means we can no longer use this class.
It may not be so much of a problem if we've written all the code we're injecting, so can add convenience zero-args constructors with default implementations, but it's very unlikely to be the case that we're not using any dependencies.
Yes, we could create i.e. a `Config` object that provides a container for the dependencies, but this then results in not-great dependency injection, and means we're sidestepping good dependency injection for the case of our library.
If you hit this, I recommend looking at the other options, such as [those called out in the Cucumber docs for _Sharing state between steps_](https://cucumber.io/docs/cucumber/state/), or my article on using [_Using Dagger for Dependency Injection with Cucumber Tests_](/posts/2021-12-30-cucumber-dagger-dependency-injection/).