Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Provider instances with Pico Container #2879

Open
mpkorstanje opened this issue May 3, 2024 · 2 comments
Open

Support Provider instances with Pico Container #2879

mpkorstanje opened this issue May 3, 2024 · 2 comments
Labels
🙏 help wanted Help wanted - not prioritized by core team ⚡ enhancement Request for new functionality

Comments

@mpkorstanje
Copy link
Contributor

mpkorstanje commented May 3, 2024

🤔 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 mpkorstanje added ⚡ enhancement Request for new functionality 🙏 help wanted Help wanted - not prioritized by core team labels May 3, 2024
@mpkorstanje
Copy link
Contributor Author

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.

public void loadGlue(Glue glue, List<URI> gluePaths) {
gluePaths.stream()
.filter(gluePath -> CLASSPATH_SCHEME.equals(gluePath.getScheme()))
.map(ClasspathSupport::packageName)
.map(classFinder::scanForClassesInPackage)
.flatMap(Collection::stream)
.filter(SpringFactory::hasCucumberContextConfiguration)
.filter(this::checkIfOfClassTypeAndNotAbstract)
.distinct()
.forEach(container::addClass);
}

  1. In the PicoFactory separate the Provider classes from the regular glue classes register them with the Pico Container at startup.

@mpkorstanje
Copy link
Contributor Author

mpkorstanje commented May 3, 2024

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
🙏 help wanted Help wanted - not prioritized by core team ⚡ enhancement Request for new functionality
Projects
None yet
Development

No branches or pull requests

1 participant