Search code examples
javacucumbercucumber-jvmgherkincucumber-java

Dynamically load step definition file from jar


I have a Gherkin executor where I execute my feature files. What I would like to do would be to add a StepDefinition file from an other jar. The user would be able to use my project with the step def that I have already wrote but he would also be able to add custom definitions from his own jar file.

Currently I have a JavaClassLoader where I load my class from my jar and I use it in my main

public class JavaClassLoader<C> extends ClassLoader {

public C LoadClass(String directory, String classpath, Class<C> parentClass) throws ClassNotFoundException {
    File pluginsDir = new File(System.getProperty("user.dir") + directory);
    for (File jar : pluginsDir.listFiles()) {
      try {
        ClassLoader loader = URLClassLoader.newInstance(
            new URL[] { jar.toURL() },
            getClass().getClassLoader()
        );
        Class<?> clazz = Class.forName(classpath, true, loader);
        Class<? extends C> newClass = clazz.asSubclass(parentClass);
        // Apparently its bad to use Class.newInstance, so we use 
        // newClass.getConstructor() instead
        Constructor<? extends C> constructor = newClass.getConstructor();
        return constructor.newInstance();

      } catch (ClassNotFoundException e) {
        // There might be multiple JARs in the directory,
        // so keep looking
        continue;
      } catch (MalformedURLException e) {
        e.printStackTrace();
      } catch (NoSuchMethodException e) {
        e.printStackTrace();
      } catch (InvocationTargetException e) {
        e.printStackTrace();
      } catch (IllegalAccessException e) {
        e.printStackTrace();
      } catch (InstantiationException e) {
        e.printStackTrace();
      }
    }
    throw new ClassNotFoundException("Class " + classpath
        + " wasn't found in directory " + System.getProperty("user.dir") + directory);
  }

}

JavaClassLoader<AbstractStepDefs> loader = new JavaClassLoader<AbstractStepDefs>();  
    loader.LoadClass("/", "stepDef.dynamicClass", AbstractStepDefs.class);  

The problem is that Cucumber isn't able to read the methods that I wrote in my other jar. Is there a way to use a step def file that isn't in the project?


Solution

  • Is there a way to use a step def file that isn't in the project?

    Yes, and no. The yes part is closer to "sort of". git supports a couple of ways of referencing subprojects within other projects. The common "subprojects" are maintained in their own project and then pulled into using projects. Look here for a discussion. I looked into doing submodules once. I even had it working. But I couldn't convince the TeamCity owners to support it. It works but you have to be careful about how you use it. There are haters.

    What I ended up doing was to create a shared "global" project that contained page files for the common login and navigation pages. This global project also contained all the startup and shutdown support for different browsers and for remote execution on SauceLabs.

    The step definitions had to be repeated (yuck; I prefer DRY) but these are small as they mostly just call the page file methods. All of these web pages are defined in the global project in their own class files. The common housekeeping code is defined in class WebDriverManager.

    @Given("^I navigate to the public ACME WebPage and select Login$")
    public void iNavigateToTheAcmePublicWebPage() {
        pageFactory.AcmePublicWebPage().navigateTo(
          WebDriverManager.instance().getAcmeUrl());
    
        pageFactory.AcmePublicWebPage().closeNotificationPopUp(); //If there is one
        pageFactory.AcmePublicWebPage().selectLoginLink();
    }
    
    @When("^I close the browser$")
    public void iCloseTheBrowser() {
        WebDriverManager.instance().closeBrowser();
    }
    

    I have reduced most, but not all duplication. Most junior test automation engineers don't have to worry about the heavy lifting as long as I maintain the global git project and notify them when they need to download a new global jar from TeamCity.