6

Hello fellow engineers!

I have run into a problem when trying to create a FAT jar to execute the Cucumber tests. Initially, I have followed the guide to set up the tests from Baeldung. The tests are working fine when executed during the Maven test phase. It is also working as expected when running the mvn exec:java command with the parameters.

However, when I have a FAT jar created and I try to execute the tests I am faced with the error

java.util.concurrent.ExecutionException: io.cucumber.core.backend.CucumberBackendException: Please annotate a glue class with some context configuration.

For example:

   @CucumberContextConfiguration
   @SpringBootTest(classes = TestConfig.class)
   public class CucumberSpringConfiguration { }
Or:

   @CucumberContextConfiguration
   @ContextConfiguration( ... )

Here is the explanation of my project, which is basically almost exactly as the test project at Baeldung.

Project Structure

RunCucumberTest.java

package com.ing.testsuite;

import io.cucumber.junit.Cucumber;
import io.cucumber.junit.CucumberOptions;
import org.junit.runner.RunWith;
import org.springframework.context.annotation.Configuration;
import io.cucumber.*;


@RunWith(Cucumber.class)
@CucumberOptions(features="src/test/resources", glue="com.ing.testsuite", plugin = {"pretty","html:target/cucumber.html"})
class RunCucumberTest {

    public static void main(String[] args) throws Throwable {
        String[] arguments = {"classpath:dummy.feature", "classpath:com.ing.testsuite"};
        io.cucumber.core.cli.Main.main(arguments);
    }
}

CucumberSpringConfiguration.java

package com.ing.testsuite;

import io.cucumber.spring.CucumberContextConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ContextConfiguration;

@CucumberContextConfiguration
@SpringBootTest(classes = RunCucumberTest.class)
public class CucumberSpringConfiguration{
}

StepDefinitions.java

package com.ing.testsuite;

import static org.junit.jupiter.api.Assertions.assertEquals;
import io.cucumber.java.en.Given;
import io.cucumber.java.en.When;
import io.cucumber.java.en.Then;

public class StepDefinitions extends CucumberSpringConfiguration{

    private Integer noOfCucumbers;
    private Integer noOfCucumberEaten;
    private Integer noOfRemainingCucumber;

    @Given("There are {int} cucumbers")
    public void cucumberTest_nrOne(Integer noOfCucumber) throws Throwable{
        noOfCucumbers = noOfCucumber;
    }

    @When("I eat {int} cucumbers")
    public void cucumberTest_nrTwo(Integer noOfCucumberEaten) throws Throwable{
        noOfCucumbers = noOfCucumbers - noOfCucumberEaten;
    }

    @Then("I should have {int} cucumbers")
    public void cucumberTest_nrThree(Integer noOfRemainingCucumber) throws Throwable{
        assertEquals(noOfCucumbers,noOfRemainingCucumber);
    }


}

dummy.feature

Feature: This is dummy test scenario
  Scenario Outline: Eating
  Given There are <start> cucumbers
  When I eat <eat> cucumbers
  Then I should have <left> cucumbers

  Examples:
  | start | eat | left |
  |  12   |  5  |  7   |
  |  20   |  5  |  15  |
  |  30   |  5  |  25  |

assembly.xml

<assembly
    xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.3 http://maven.apache.org/xsd/assembly-1.1.3.xsd">
    <id>fat-tests</id>
    <formats>
        <format>jar</format>
    </formats>
    <includeBaseDirectory>false</includeBaseDirectory>
    <dependencySets>
        <dependencySet>
            <outputDirectory>/</outputDirectory>
            <useProjectArtifact>true</useProjectArtifact>
            <unpack>true</unpack>
            <scope>test</scope>
        </dependencySet>
    </dependencySets>
    <fileSets>
        <fileSet>
            <directory>${project.build.directory}/test-classes</directory>
            <outputDirectory></outputDirectory>
            <includes>
                <include>**/*.*</include>
               </includes>
            <useDefaultExcludes>true</useDefaultExcludes>
        </fileSet>
        <fileSet>
            <directory>${project.build.directory}/test-classes</directory>
            <outputDirectory></outputDirectory>
            <includes>
                <include>*.feature</include>
            </includes>
            <useDefaultExcludes>true</useDefaultExcludes>
        </fileSet>
    </fileSets>
</assembly>

Some of the resources I have already tried to follow

I would really appreciate if someone could help me out.

2
  • why are you passing those params to io.cucumber.core.cli.Main.main(arguments)? Commented Dec 7, 2021 at 15:07
  • You're clobbering the service descriptors in META-INF. Configure your maven plugin to merge them. Commented Dec 7, 2021 at 19:40

2 Answers 2

2

I just had the same issue but with Gradle. I was trying to run Cucumber tests from a jar file and was getting an identical error.

Short answer

In build.gradle I've added next code:

 shadowJar {
     ....
     transform(AppendingTransformer) {
        resource = 'META-INF/services/io.cucumber.core.backend.BackendProviderService'
     }
 }

Analogy in Maven would be something like:

<transformers>
    <transformer implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
        <resource>META-INF/services/io.cucumber.core.backend.BackendProviderService</resource>
    </transformer>
</transformers>

A little longer explanation

After debugging for some time (thanks Yulia) we found out how cucumber looks for Glue (Glue classes are all the classes that cucumber requires for run - stepdef classes, contextconfigurator, ...).

It looks for those classes with BackendService(es), instances of which are getting created at some point.

In the successful case there were 2 services instantiated:

  • io.cucumber.java.JavaBackendProviderService
  • io.cucumber.spring.SpringBackendProviderService

In the failed case (found out by debugging run from jar) there was only one:

  • io.cucumber.java.JavaBackendProviderService

Turned out that which BackendService to instantiate exactly it finds in this file - META-INF/services/io.cucumber.core.backend.BackendProviderService.

Unpacking the jar showed that there is indeed only one service listed - Java.

No Spring backend service instantiated means it couldn't find the Spring components.

These files are coming from the cucumber libs:

  • Java from cucumber-java
  • Spring from cucumber-spring

So it boils down to our packaging process not being able to merge these 2 files together, it was picking the first one and not applying the second one.

When running from the IDE they seem to be merged fine.

So adding the code above says packaging task (shadowJar in my case) how to deal with this file, adds a rule to merge all the files with this name it will find (in META-INF of course).

I hope you have already solved your issue, and if not - I hope my experience will help with your problem as well.

Sign up to request clarification or add additional context in comments.

1 Comment

I have exactly the same issue, using cucumber/junit 5 with simple spring, everything works from Intellij with @Suite annotated starter class, it also works if I programatically create a junit platform Discovery job and execute it, however when I package it with maven assembly that Spring backend class is not initiated therefore dependency injection is not working in the step classes.
0

Maybe it's too late, but I found the solution. In the shadowJar config section add mergeServiceFiles() line, this should merge all the conflicting services into one file.

So, it should be something like:

tasks.withType<ShadowJar> {
    // ...
    mergeServiceFiles()
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.