Search code examples
javacucumbercucumber-jvmcucumber-javacucumber-junit

Is there a way to override getDescription() in io.cucumber.junit.Cucumber.java after 5.0.0?


I'm trying to uplift an existing project from 3.0.2 to 5.5.0. The project has a custom implementation of getDescription() in the Cucumber.java runner class, and the functionality has to be maintained as it has dependencies in other projects. The JUnit version is 4.12.

In 3.0.2, the Cucumber.java runner class is defined in cucumber.api.junit.Cucumber as

public class Cucumber extends ParentRunner<FeatureRunner>

which makes the custom runner pretty straightforward:

public class DescriptiveCucumberRunner extends Cucumber
{
    public DescriptiveCucumberRunner(Class<?> testClass) throws InitializationError, IOException
    {
        super(testClass);
    }

    @Override
    public Description getDescription()
    {
        String text = "Cucumber tests running on " + new Date();
        Description originalDescription = super.getDescription();
        Description newDescription = originalDescription.childlessCopy();
        newDescription.addChild(Description.createSuiteDescription(text));

        for(Description childDescription : originalDescription.getChildren())
        {
            newDescription.getChildren().get(0).addChild(childDescription);
        }
        return newDescription;
    }
}

In 5.0.0, the Cucumber.java runner class was moved to io.cucumber.junit.Cucumber and the definition was changed to

public final class Cucumber extends ParentRunner<ParentRunner<?>>

so I'm no longer able to extend.

I've tried extending org.junit.runners.ParentRunner

public class DescriptiveCucumberRunner extends ParentRunner<Cucumber>

and aggregating with a delegate, but because Cucumber.java's methods are protected I would essentially have to copy the whole class to retain current functionality, which wouldn't really be a solution at all. I could use reflection to call the methods defined in Cucumber.java, but that doesn't seem very clean either.

Does anyone know how to do this without requiring changes in other projects? Maybe there's an intended way to do it with Cucumber that I'm oblivious to? Hopefully I'm just missing an obvious solution on this one, but so far I'm drawing a blank.


Solution

  • It is usually preferable to use composition over inheritance. So rather then extending the existing the Cucumber runner class you may want to consider implementing your own runner class that has an instance of the Cucumber runner as one of its children.

    package com.example;
    
    import io.cucumber.junit.Cucumber;
    import org.junit.runner.Description;
    import org.junit.runner.notification.RunNotifier;
    import org.junit.runners.ParentRunner;
    import org.junit.runners.model.InitializationError;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class MyCustomRunner extends ParentRunner<Cucumber> {
    
        private final List<Cucumber> children = new ArrayList<>(1);
    
        protected MyCustomRunner(Class<?> testClass) throws InitializationError {
            super(testClass);
            this.children.add(new Cucumber(testClass));
        }
    
        @Override
        public Description getDescription() {
            return // your custom description;
        }
    
        @Override
        protected List<Cucumber> getChildren() {
            return children;
        }
    
        @Override
        protected Description describeChild(Cucumber child) {
            return child.getDescription();
        }
    
        @Override
        protected void runChild(Cucumber cucumber, RunNotifier runNotifier) {
            cucumber.run(runNotifier);
        }
    }