Tuesday 1 December 2020

Cucumber set up for SpringBoot and JUnit 4 or JUnit 5

Cucumber has worked well with pure JUnit 4 projects for quite some time.  The transition to JUnit 5 does change things slightly but the change isn't particularly bad once things are set up. Here are the versions that I've been using for these tests

SpringBoot: 2.4.0
Cucumber: 6.9.0
maven.surefire.plugin: 2.22.2
maven.failsafe.plugin: 2.22.2

JUnit4

pom.xml

The Pom must include the dependencies as follows (versions have been dropped off here)

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
        </dependency>

CucumberIT.java

The CucumberIT class is the bootstrap part of the cucumber testing.  We see here the JUnit4 @RunWith.  The feature files are set here to be in the src/tests/resources/features directory.

import io.cucumber.junit.CucumberOptions;
import io.cucumber.junit.Cucumber;
import org.junit.runner.RunWith;

@RunWith(Cucumber.class)
@CucumberOptions(
    features = "src/test/resources/features",
    tags = "",
    plugin = {"pretty", "json:target/cucumber.json"})
public class CucumberIT {}

CucumberSpringContext.java

This is where the spring wiring gets done to make sure that SpringBoot starts.  I've also included @AutoConfigureMockMvc for making rest calls to the spring boot service from the Step definitions but you don't have to include that.  If you have additional spring configuration you can add @Beans into this class but you need to include the @ContextConfiguration spring annotation too.  In previous versions of cucumber before the @CucumberContextConfiguration annotation was available you needed to have a blank cucumber @Before in this class to make sure it was found.

In fact once this is set up it stays the same for JUnit 4 and 5 because it is primarily a spring configuration not a Cucumber one!

import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;

@CucumberContextConfiguration
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class CucumberContextTestConfiguration {}

Feature file and properties

As previously mentioned the feature files now go into the src/test/resources/features directory which is referenced in the CucumberIT.  No other properties are necessary for JUnit4


Running with JUnit5

The transition to JUnit5 can happen in two stages.  Firstly just running with JUnit5 can be backwards compatible with all the current JUnit4 annotations and setup with minimal changes.  Here are the changes that need to be made.

Also check out details about the surefire and failsafe plugins which have had problems with JUnit5 before versions 2.22.0 because they can't find JUnit5 tests.

pom.xml

The only differences here are that the junit:junit:4 dependency comes out and the JUnit5 dependencies come in.  Make sure you include the junit-vintage-engine which is what provides the backwards compatibility for JUnit4 annotations, imports etc


        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <version>${cucumber.version}</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.vintage</groupId>
            <artifactId>junit-vintage-engine</artifactId>
            <scope>test</scope>
        </dependency>

CucumberIT.java

This stays the same

Feature file and properties

The feature file stays as was because it is still directly referenced by the CucumberIT class.  However, there is now a warning from Cucumber about the cucumber report.  This can be removed by including a new file cucumber.properties in src/test/resources

cucumber.properties

cucumber.publish.enabled=false
cucumber.publish.quiet=true


Full JUnit5

Now for fully transitioning to run cucumber with JUnit 5.  The test discovery mechanism has changed between JUnit4 and JUnit5 which is the reason for much of the following change. There are no longer any @CucumberOptions so these have to be specified in property files instead.

pom.xml

The junit-vantage-engine dependency has gone and the cucumber-java dependency is replaced with a JUnit5 specific one.  Again the specific versions have been ignored here



        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-java</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-junit-platform-engine</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.cucumber</groupId>
            <artifactId>cucumber-spring</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>

CucumberIT.java

The JUnit4 annotations are no longer available so we use the new JUnit5 annotation - note the different import path.  

import io.cucumber.junit.platform.engine.Cucumber;

@Cucumber
public class CucumberIT {}

Feature file and properties

By default the feature files need to be in the same package as the CucumberIT class (the class annotated with @Cucumber) so they are moved.  The cucumber.properties file that we introduced earlier now has to be renamed to junit-platform.properties (but it stays in src/test/resources).  Also, because the @CucumberOptions no longer exists we can include the plugin options here too

junit-platform.properties

cucumber.plugin=pretty,json:target/cucumber.json
cucumber.publish.enabled=false
cucumber.publish.quiet=true

4 comments:

  1. This article has helped me in upgrading from JUnit4 to JUnit5 for cucumber tests. So, when running tests in JUnit4, I had the option to use BeforeClass and AfterClass. For the cucumber equivalent, I had to try and test various ways. https://www.metamorphant.de/blog/posts/2020-03-10-beforeall-afterall-cucumber-jvm-junit/ might get in handy if anyone else is looking for the same

    ReplyDelete
  2. Great article, covering the changes required between versions thanks.

    ReplyDelete
    Replies
    1. I'm glad it helped and thanks for taking the time to comment.

      Delete
  3. "By default the feature files need to be in the same package as the CucumberIT class" - this is _not_ "by default". Currently it is the _only_ way. See below for what authors have to say about this.

    https://stackoverflow.com/questions/65638133/cucumber-with-junit5-and-java8

    Or see this corresponding bug: https://github.com/cucumber/cucumber-jvm/issues/2175

    ReplyDelete