6

I am creating a small testing framework that should utilize both Cucumber and the Spring Boot platform. The idea is to let the whole application be packaged as a single jar and run after the BDD features have been properly parametrized.

The framework starts in command line runner mode like this:

public class FwApplication implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(FwApplication.class, args);
    }

    @Override
    public void run(String... arg0) throws Exception {
        JUnitCore.main(CucumberIntegration.class.getCanonicalName());
    }
}

Then there is the CucumberIntegration class:

@RunWith(Cucumber.class)
@CucumberOptions(features = "config/features")
@ContextConfiguration(classes= AppConfiguration.class)
public class CucumberIntegration {
}

I have also some simple tests which run fine under my IDE, but when I try to package the application and run it over java -jar fw-0.0.1-SNAPSHOT.jar I get to see following:

There was 1 failure:
1) initializationError(com.fmr.bddfw.test.CucumberIntegration)
cucumber.runtime.CucumberException: No backends were found. Please make sure you have a backend module on your CLASSPATH.
        at cucumber.runtime.Runtime.<init>(Runtime.java:81)
        at cucumber.runtime.Runtime.<init>(Runtime.java:70)
        (...)

All necessary jars are already in the one jar created by maven and it works fine under my IDE.

Any ideas what could help?

EDIT: Here my pom file.

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xxx</groupId>
    <artifactId>fw</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>fw</name>
    <description>BDD Testing Framework</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.3.RELEASE</version>
        <relativePath /> <!-- lookup parent from repository -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <cucumber.version>1.2.5</cucumber.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>

        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-junit</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-spring</artifactId>
            <version>${cucumber.version}</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
3
  • You should add cucumber-java dependency Commented May 19, 2017 at 15:37
  • Does not seem to be the case. cucumber-spring depends on cucumber-java. The jars are included. And it works in the IDE. :/ Commented May 19, 2017 at 18:20
  • Sorry to resurrect this post, but did you ever find out a solution? I can't find a way to do the resource loader while also keeping my tests separated as they are. As they currently stand, I can run each one in my IDE just fine. I would hate to break everything. Commented Sep 18, 2019 at 14:22

5 Answers 5

5

Using the suggestion given by Marcus:

Step1: Create your custom MultiLoader class:

package cucumber.runtime.io;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

public class CustomMultiLoader implements ResourceLoader {
    public static final String CLASSPATH_SCHEME = "classpath*:";
    public static final String CLASSPATH_SCHEME_TO_REPLACE = "classpath:";
    private final ClasspathResourceLoader classpath;
    private final FileResourceLoader fs;
    public CustomMultiLoader(ClassLoader classLoader) {
        classpath = new ClasspathResourceLoader(classLoader);
        fs = new FileResourceLoader();
    }
    @Override
    public Iterable<Resource> resources(String path, String suffix) {
        if (isClasspathPath(path)) {
            PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
            String locationPattern = path.replace(CLASSPATH_SCHEME_TO_REPLACE, CLASSPATH_SCHEME) + "/**/*" + suffix;
            org.springframework.core.io.Resource[] resources;
            try {
                resources = resolver.getResources(locationPattern);
            } catch (IOException e) {
                resources = null;
                e.printStackTrace();
            }
            return convertToCucumberIterator(resources);
        } else {
            return fs.resources(path, suffix);
        }
    }
    private Iterable<Resource> convertToCucumberIterator(org.springframework.core.io.Resource[] resources) {
        List<Resource> results = new ArrayList<Resource>();
        for (org.springframework.core.io.Resource resource : resources) {
            results.add(new ResourceAdapter(resource));
        }
        return results;
    }

    public static String packageName(String gluePath) {
        if (isClasspathPath(gluePath)) {
            gluePath = stripClasspathPrefix(gluePath);
        }
        return gluePath.replace('/', '.').replace('\\', '.');
    }

    private static boolean isClasspathPath(String path) {
        if (path.startsWith(CLASSPATH_SCHEME_TO_REPLACE)) {
            path = path.replace(CLASSPATH_SCHEME_TO_REPLACE, CLASSPATH_SCHEME);
        }
        return path.startsWith(CLASSPATH_SCHEME);
    }

    private static String stripClasspathPrefix(String path) {
        if (path.startsWith(CLASSPATH_SCHEME_TO_REPLACE)) {
            path = path.replace(CLASSPATH_SCHEME_TO_REPLACE, CLASSPATH_SCHEME);
        }
        return path.substring(CLASSPATH_SCHEME.length());
    }

}

Step2: Create an adapter between org.springframework.core.io.Resource and cucumber.runtime.io.Resource:

package cucumber.runtime.io;

import java.io.IOException;
import java.io.InputStream;

public class ResourceAdapter implements Resource {
    org.springframework.core.io.Resource springResource;

    public ResourceAdapter(org.springframework.core.io.Resource springResource) {
        this.springResource = springResource;
    }

    public String getPath() {
        try {
            return springResource.getFile().getPath();
        } catch (IOException e) {
            try {
                return springResource.getURL().toString();
            } catch (IOException e1) {
                e1.printStackTrace();
                return "";
            }
        }
    }

    public String getAbsolutePath() {
        try {
            return springResource.getFile().getAbsolutePath();
        } catch (IOException e) {
            return null;
        }
    }

    public InputStream getInputStream() throws IOException {
        return this.springResource.getInputStream();
    }

    public String getClassName(String extension) {

        String path = this.getPath();
        if (path.startsWith("jar:")) {
            path = path.substring(path.lastIndexOf("!") + 2);
            return path.substring(0, path.length() - extension.length()).replace('/', '.');
        } else {
            path = path.substring(path.lastIndexOf("classes") + 8);
            return path.substring(0, path.length() - extension.length()).replace('\\', '.');
        }

    }
}

Step3: Create your custom main class that uses your CustomMultiLoader:

package cucumber.runtime.io;

import static java.util.Arrays.asList;

import java.io.IOException;
import java.util.ArrayList;

import cucumber.runtime.ClassFinder;
import cucumber.runtime.Runtime;
import cucumber.runtime.RuntimeOptions;

public class CucumberStaticRunner {

    public static void startTests(String[] argv) throws Throwable {
        byte exitstatus = run(argv, Thread.currentThread().getContextClassLoader());
        System.exit(exitstatus);
    }

    public static byte run(String[] argv, ClassLoader classLoader) throws IOException {
        RuntimeOptions runtimeOptions = new RuntimeOptions(new ArrayList<String>(asList(argv)));

        ResourceLoader resourceLoader = new CustomMultiLoader(classLoader);
        ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
        Runtime runtime = new Runtime(resourceLoader, classFinder, classLoader, runtimeOptions);
        runtime.run();
        return runtime.exitStatus();
    }
}

Step4: Call your custom main class instead of cucumber.api.cli.Main.main:

String[] cucumberOptions = { "--glue", "my.test.pack", "--no-dry-run", "--monochrome", "classpath:features" };
CucumberStaticRunner.startTests(cucumberOptions);
Sign up to request clarification or add additional context in comments.

2 Comments

I know it has been a long time, but could you help me out figuring this out in the current version (4.7.1) of the cucumber library? I posted it at stackoverflow.com/questions/57995598/…
I have followed 4 steps mentioned above in my sping boot application, now it is able to load the .feature file and step definitions. But the problem is inside step definition java files I autowired few properties from application.properties, which is not working. could you please help me on this.
3

Fixed it by following configuration:

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <configuration>
       <requiresUnpack>
           <dependency>
                 <groupId>info.cukes</groupId>
                 <artifactId>cucumber-java</artifactId>
          </dependency>
       </requiresUnpack>
    </configuration>
</plugin>

Comments

-1

You should add cucumber-java dependency

<dependency>
    <groupId>info.cukes</groupId>
    <artifactId>cucumber-java</artifactId>
    <version>${cucumber.version}</version>
</dependency>

1 Comment

No, this is not needed. cucumber-spring is dependent on cucumber-java.
-1
private static byte run(String[] argv, ClassLoader classLoader) throws IOException {

    // cucumber/Spring Boot classloader problem
    // CucumberException: No backends were found. Please make sure you have a backend module on your CLASSPATH

    RuntimeOptions runtimeOptions = new RuntimeOptions(new ArrayList<String>(asList(argv)));
    ResourceLoader resourceLoader = new MultiLoader(classLoader);
    ClassFinder classFinder = new ResourceLoaderClassFinder(resourceLoader, classLoader);
    Reflections reflections = new Reflections(classFinder);
    List<Backend> list = new ArrayList<>();
    list.addAll(reflections.instantiateSubclasses(Backend.class, "cucumber.runtime", new Class[]{ResourceLoader.class}, new Object[]{resourceLoader}));
    if (list.size() == 0) {
        JavaBackend javaBackend = new JavaBackend(resourceLoader);
        list.add(javaBackend);
    }
    Runtime runtime = new Runtime(resourceLoader, classLoader, list, runtimeOptions);
    runtime.run();
    return runtime.exitStatus();
}

1 Comment

Could you give some more context? How can this be used in the OP case (Spring Boot + JUnit)?
-2

I used below code in POM.xml file

        <dependency>
            <groupId>info.cukes</groupId>
            <artifactId>cucumber-java</artifactId>
            <version>1.2.4</version>
            <scope>test</scope>
        </dependency>

It is working fine now.

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.