Using Dagger for Dependency Injection with Cucumber Tests

Featured image for sharing metadata for article

Why Dependency Injection?

Cucumber is a great framework for building test automation tools with human-readable language 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 statics 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 Angie Jones' blog post Sharing State Between Cucumber Steps with Dependency Injection.

However as I've blogged about, 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, I instead recommend Dagger. As noted in Lightweight and Powerful Dependency Injection for JVM-based Applications with Dagger, 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.

Let us say that we're following a common practice of using constructor injection for objects, such as the below code snippet:

package io.cucumber.skeleton;

import io.cucumber.java.en.Given;

public class StepDefinitions {

    private final Belly belly;

    public StepDefinitions(Belly belly) {
        this.belly = belly;
    }

    @Given("I have {int} cukes in my belly")
    public void I_have_cukes_in_my_belly(int cukes) {
        belly.eat(cukes);
    }
}

And the following Belly class definition:

package io.cucumber.skeleton;

public class Belly {
    public void eat(int cukes) {

    }
}

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):

 dependencies {
+    testAnnotationProcessor("com.google.dagger:dagger-compiler:2.40.5")
+    testImplementation("com.google.dagger:dagger:2.40.5")
     testImplementation(platform("org.junit:junit-bom:5.8.2"))
     testImplementation(platform("io.cucumber:cucumber-bom:7.1.0"))

     testImplementation("io.cucumber:cucumber-java")
-    testImplementation("io.cucumber:cucumber-picocontainer")
     testImplementation("io.cucumber:cucumber-junit-platform-engine")
     testImplementation("org.junit.platform:junit-platform-suite")
     testImplementation("org.junit.jupiter:junit-jupiter")

Then we'd set up a Config class that would define which of the objects to be injected:

package io.cucumber.skeleton.config;

import dagger.Component;
import io.cucumber.skeleton.Belly;

import javax.inject.Singleton;

@Singleton
@Component(modules = {ConfigModule.class})
public interface Config {
    Belly belly();
}

Which are produced by the configuration module:

import dagger.Module;
import dagger.Provides;
import io.cucumber.skeleton.Belly;

import javax.inject.Singleton;

@Module
public class ConfigModule {

    private ConfigModule() {
        throw new UnsupportedOperationException("Utility class");
    }

    @Provides
    @Singleton
    public static Belly belly() {
        return new Belly();
    }
}

Which we need to modify the StepDefinitions, to utilise the new Dagger-built configuration:

 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 this will be fine, and if we are, we can provide a test-only constructor to inject in mock configuration:

package io.cucumber.skeleton;

import io.cucumber.java.en.Given;
import io.cucumber.skeleton.config.Config;
import io.cucumber.skeleton.config.DaggerConfig;

public class StepDefinitions {

    private final Belly belly;

    public StepDefinitions() {
        this(DaggerConfig.create());
    }

    StepDefinitions(Config config) {
        this.belly = config.belly();
    }

    @Given("I have {int} cukes in my belly")
    public void I_have_cukes_in_my_belly(int cukes) {
        belly.eat(cukes);
    }
}

Configurability

As mentioned in Lightweight and Powerful Dependency Injection for JVM-based Applications with Dagger, 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.

Written by Jamie Tanna's profile image Jamie Tanna on , and last updated on .

Content for this article is shared under the terms of the Creative Commons Attribution Non Commercial Share Alike 4.0 International, and code is shared under the MIT License.

#blogumentation #java #cucumber #dagger.

This post was filed under articles.

Interactions with this post

Interactions with this post

Below you can find the interactions that this page has had using WebMention.

Have you written a response to this post? Let me know the URL:

Do you not have a website set up with WebMention capabilities? You can use Comment Parade.