'Running Cucumber in Springboot with Maven in executable JAR
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.
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
- Running Cucumber tests directly from executable jar
- SpringRunner unable to detect configuration
- Spring context from within a jar file
- Is additional context configuration required when upgrading cucumber-jvm from version 4 to version 6?
- Initialization Error while executing TestRunner class
I would really appreciate if someone could help me out.
Solution 1:[1]
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.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Oleksii Karnatskyi |