cucumber / cucumber-jvm

Cucumber for the JVM
https://cucumber.io
MIT License
2.69k stars 2.02k forks source link

Support Provider instances with Pico Container #2879

Open mpkorstanje opened 2 months ago

mpkorstanje commented 2 months ago

🤔 What's the problem you're trying to solve?

Currently when using Cucumber with cucumber-picocontainer it is not possible to inject dependencies that that ultimately do not have a zero-argument constructor or require some setup. For example:

For example

public class MyStepDefintions {
  public MyStepDefinitions(WebDriver driver) {
    ...
  }
}

And it is also not possible to inject dependencies that partially depend on other inject dependencies and partially on some configuration.

For example we may want to inject PageObjects that partially depend on other dependencies and partially on some configuration:

SecuredPage securedPage = new SecuredPage(driver, project, "example", "top secret");

✨ What's your proposed solution?

Support Pico Containers Providers so that it becomes possible to write.

public class WebDriverProvider implement Provider {

    public WebDriver provide() {
        return // create WebDriver instance here; 
    }
}
public class SecuredPageProvider implements Provider {
    public SecuredPage provide(WebDriver driver, Project project) {
         return new SecuredPage(driver, project, "example", "top secret");
    }
}

⛏ Have you considered any alternatives or workarounds?

The current work around is to create a container object. But that quickly becomes ugly once multiple dependencies are involved. The other alternative is to use cucumber-spring or cucumber-guice but both are quite complicated indeed.

📚 Any additional context?

mpkorstanje commented 2 months ago

I currently don't have much time to implement this. So if anyone wants to help out, that would be much appreciated.

To implement this the following needs to be done.

  1. Create a PicoBackend class that implements the Backend interface and scans the glue path for classes that implement Provider. This can be quite similar to the SpringBackend. But swapping out .filter(SpringFactory::hasCucumberContextConfiguration) for the check that a class implements Provider.

https://github.com/cucumber/cucumber-jvm/blob/5abf447c854f58148d17a2a7610f4cc7998e3719/cucumber-spring/src/main/java/io/cucumber/spring/SpringBackend.java#L29-L39

  1. In the PicoFactory separate the Provider classes from the regular glue classes register them with the Pico Container at startup.
mpkorstanje commented 2 months ago

On second thought. While the Provider interface is quite nice, there are some problems.

  1. I don't really like the marker interface in combination with magically named "provide" method. It is too easy to get it wrong.
  2. It exposes Pico container details that were invisible in the past.
  3. The provider must have a zero arg constructor. This is really old fashioned.
  4. There can only be a single type created per provider. This creates lots of repetition.

So it might be nicer to declare a proper factory object:

@Factory
public class WebDriverFactory {
   private final Project project;

   public WebDriverFactory(Project project) {
      this.project = project;
   }

   @Bean
   public WebDriver webDriver() {
       return // create WebDriver 
   }

   @Bean
    public SecuredPage provide(WebDriver driver) {
       return new SecuredPage(driver, project, "example", "top secret");
    }
}

But this isn't great either. Because now we have 2 annotations, to mark the factory class and methods involved. I'm also not sure if PicoContainer supports something like this. Or what circular dependencies might be created this way.

This could be simplified a bit by requiring the factory methods to be static.

public class WebDriverFactory {

   @Bean
   public static WebDriver webDriver() {
       return // create WebDriver 
   }

   @Bean
    public static SecuredPage provide(WebDriver driver, Project project) {
       return new SecuredPage(driver, project, "example", "top secret");
    }
}

But at point we may as well recommend that people use a fully featured DI such as Spring or Guice. Perhaps what we need instead are some good examples.