Git Product home page Git Product logo

Comments (35)

sormuras avatar sormuras commented on September 26, 2024 1

Paul, you may inject any instance you want. You just have write your own ParameterResolver, like:

class WebDriverExtension implements ParameterResolver {
  @Override
  public boolean supportsParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
    return parameterContext.getParameter().getType().equals(WebDriver.class);
  }

  @Override
  public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
    return new WebDriver(); // ChromeDriver ... what-ever
  }
}

And, register the extension with the test class:

@ExtendWith(WebDriverExtension.class)
class WebTests {

  private final WebDriver driver;

  WebTests(WebDriver driver) {
    this.driver = driver;
  }

  @Test
  void login() {
    // use driver field here
  }

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024 1

Cool. Presumably there will be community contributions of GuiceJunit5ParameterResolver, DaggerJunit5ParameterResolver and from me, PicoContainerJunit5ParameterResolver

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024 1

And PicoContainer team, seeing as I was on the committee too.

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

@paul-hammant, are you planning on creating a PR for such examples?

If not, what exactly are you proposing/asking?

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

FWIW, the MockitoExtension and SpringExtension both demonstrate how to make use of DI in JUnit Jupiter tests.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

My bad, I didn't look into MockitoExtension or SpringExtension.

from junit5-samples.

marcphilipp avatar marcphilipp commented on September 26, 2024

If you want to share state between multiple test cases you can store objects in the root extension context. Please let me know if you need additional help.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

I looked at the Mockito sample, and I couldn't see constructor injection going on (as described http://junit.org/junit5/docs/current/user-guide/#writing-tests-dependency-injection). I couldn't find a Spring sample within this repo. I'll check other repos, and circle back.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Could be one of https://github.com/search?q=SpringExtension+junit5&type=Code I guess - please advise @marcphilipp . PS I'm co-creator of PicoContainer the first DI container for constructor injection, and co-creator of Selenium (v1), hence the interest in DI and Selenium in a JUnit5 context for pure test code (as opposed to the AUT).

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

OK, I've completely misunderstood - The DI into ctors is ONLY of types core to JUnit itself: TestInfo for one, and maybe more. I was expecting it to have a general DI capability. For example, I'd like to be able to inject in a WebDriver instance (in addition to or instead of TestInfo as in the example code). This being a feature I enjoy from TestNG (with one pesky bug that was marked as won't fix some years ago).

from junit5-samples.

sormuras avatar sormuras commented on September 26, 2024

https://github.com/junit-pioneer/junit-pioneer might be good place to host some of them.

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

I looked at the Mockito sample, and I couldn't see constructor injection going on (as described http://junit.org/junit5/docs/current/user-guide/#writing-tests-dependency-injection). I couldn't find a Spring sample within this repo. I'll check other repos, and circle back.

I didn't mean that there is an example using the SpringExtension in this repo.

The SpringExtension is naturally in the Spring Framework repo. 😉

It supports DI for constructors and test methods by implementing the ParameterResolver extension API from JUnit Jupiter, and it supports standard DI for @Autowired fields and @Autowired setter methods by implementing the TestInstancePostProcessor extension API from JUnit Jupiter.

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

You can see some basic examples of DI support with the SpringExtension in Spring's test suite here:

Hope that gives you an idea of the power of DI in JUnit Jupiter! 😎

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

OK, I've completely misunderstood - The DI into ctors is ONLY of types core to JUnit itself: TestInfo for one, and maybe more.

That's incorrect: a ParameterResolver can be used by any third party to provide arguments to constructors, lifecycle methods (e.g., @BeforeEach), and test methods (e.g., @Test, @TestFactory).

For example, these lines show that parameters can be resolved by Spring and JUnit Jupiter for the exact same constructor.

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

@paul-hammant, if you do provide such an extension, I would highly recommend against naming it PicoContainerJunit5ParameterResolver.

"JUnit 5" is an umbrella project for the Platform, Jupiter, and Vintage.

Any implementation of ParameterResolver is therefore related to "JUnit Jupiter" and not to "JUnit 5".

A better name would be PicoContainerParameterResolver.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Yup, as @sormuras has said yesterday. I stand corrected. But one pedantic (sorry) correction @Inject is the "standard DI", not @Autowired - https://github.com/google/guice/wiki/JSR330

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

No need for a "correction": I am fully aware of @Inject, JSR-330, and the combined efforts of the Guice and Spring teams to make that happen. 😉

I was actually referring to Spring's standard DI support, not JSR 330, meaning that Spring does not have to interoperate with JUnit Jupiter in order to perform DI for fields or setter methods.

And that's an important distinction for anyone interested in providing DI support via an extension for JUnit Jupiter.

  1. for constructor injection, lifecycle method injection, and test method injection, one has to implement ParameterResolver and provide individual parameters that way.
  2. for all other forms of injection, there are no restrictions as long as one obtains access to the test instance by implementing TestInstancePostProcessor.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Not sure if I should make a new GH issue for this or not. Or go to a mail-list...

Work in progress:

https://github.com/paul-hammant/JUnit5_DependencyInjection_WebDriver

I'm not sure how to participate in DI scopes.

I want one instance of the WebDriver for the entire run. I'm getting one instance of it per test class. That's because WebDriverExtension is freshly instantiated per test. I would have expected it it not to be and a scope argument passed into resolveParameter.

Classic "web application" scopes:

  1. Applications
  2. Session
  3. Request

I'd imagine for test execution (different to web application), there would be:

  1. Run
  2. Suite
  3. Package
  4. TestClass
  5. TestMethod

'Tag' is too cross-cutting to be part of a scope parent/child hierarchy, I think

Secondarily, how do I participate in the lifecycle of scopes. I want to .quit() WebDriver at the end of the "run". Remember, I'm co-creator of Selenium back in 2004, so I've developed personal best practices for it over 13 years now :)

Thoughts @sormuras ? And thanks for the 'register the extension' link above.

from junit5-samples.

sormuras avatar sormuras commented on September 26, 2024

@paul-hammant See section http://junit.org/junit5/docs/snapshot/user-guide/#extensions-keeping-state

Using the ExtensionContext and the optional getParent() accessor you may store and retrieve a "singleton" webdriver for the "run" scope.

Like:

@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
    WebDriver driver = (WebDriver) extensionContext.getParent().get().getStore().getOrComputeIfAbsent(KEY, key -> Driver.create(...));
    return driver;
}

from junit5-samples.

sormuras avatar sormuras commented on September 26, 2024

Btw, I filed junit-team/junit5#925 for retrieving the root extension context more conveniently.

from junit5-samples.

sormuras avatar sormuras commented on September 26, 2024

Regarding the "lifecycle of scopes" you may hook into the following extension points:
lifecycle
I guess, the AfterAllCallback is the one you're looking for when it comes to .quit().

Another option could be to install a TestExecutionListener on the platform level and somehow trigger the destruction of the WebDriver within the testPlanExecutionFinished(TestPlan) implementation.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

@sormuras Thanks for the time you're spending on this, and the info and links above. The 'Keeping state' section made me smile when I saw 'container' language worked in.

I've pushed a new commit to my nascent WebDriver example repo. I have implemented the AfterAllCallback interface on the WebDriverExtension class. However, the afterAll(..) method is being called twice for some reason :-(

Selenium guards against normal methods being invoked on it after quit() of course, so the problem was clear even before I used the debugger.

So superficially, it appears that AfterAllCallback is invoked at the moments I would expect AfterEachCallback to be for.

Is that a bug, or expected behavior?

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Also @sormuras, as I review the 'Relative Execution Order of User Code and Extensions', I'm thinking there is a simpler hierarchical representation:

BeforeAllCallback
@BeforeAll
    BeforeEachCallback
    @BeforeEach
        BeforeTestExecutionCallback
        @Test
        TestExecutionExceptionHandler
        AfterTestExecutionCallback
    @AfterEach
    AfterEachCallback
@AfterAll
AfterAllCallback

Thoughts?

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Comments in the pull-request, @marcphilipp

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Also, I'd like to comment that I understand this DI business is hard, and that I appreciate the time spent humoring me on this. TestNG had DI from some years ago onwards. However, the implementation is incomplete - there is a concept called a listener, that can't participate in the DI container/contained world. Ref issue testng-team/testng#279

from junit5-samples.

marcphilipp avatar marcphilipp commented on September 26, 2024

I've pushed a new commit to my nascent WebDriver example repo. I have implemented the AfterAllCallback interface on the WebDriverExtension class. However, the afterAll(..) method is being called twice for some reason :-(

Selenium guards against normal methods being invoked on it after quit() of course, so the problem was clear even before I used the debugger.

So superficially, it appears that AfterAllCallback is invoked at the moments I would expect AfterEachCallback to be for.

Is that a bug, or expected behavior?

That's expected behavior: AfterAllCallbacks are invoked after all tests in a test class have been invoked, AfterEachCallback are invoked after each test method. You're looking for a callback to be invoked after all test classes have been run. I'm afraid there's currently no such callback. However, for the time being you can use JVM shutdown hooks as a temporary workaround:

@Override
public Object resolveParameter(ParameterContext parameterContext, ExtensionContext extensionContext) throws ParameterResolutionException {
	ExtensionContext.Store store = getRoot(extensionContext).getStore();
	return store.getOrComputeIfAbsent(WebDriver.class, webDriverClass -> {
		LOG.info("Creating ChromeDriver");
		ChromeDriver chromeDriver = new ChromeDriver();
		Runtime.getRuntime().addShutdownHook(new Thread(() -> {
			LOG.info("Quitting ChromeDriver");
			chromeDriver.quit();
		}));
		return chromeDriver;
	});
}

I've submitted that code as a pull request to your repo:
paul-hammant/JUnit5_DependencyInjection_WebDriver#1

There's junit-team/junit5#742 to introduce lifecycle support for objects in ExtensionContext.Store. I think instead of closing objects that implement Closable, we could introduce a mechanism that's similar to a shutdown hook but guaranteed to be called when closing the corresponding ExtensionContext. That would make it more explicit. While automatically closing Closable instances sounds convenient, I'm not sure that's what all extension authors would want. And it's certainly not obvious.

from junit5-samples.

marcphilipp avatar marcphilipp commented on September 26, 2024

Also @sormuras, as I review the 'Relative Execution Order of User Code and Extensions', I'm thinking there is a simpler hierarchical representation.

The reason BeforeEachCallback and @BeforeEach are indented differently is that first all BeforeEachCallbacks are executed and then all @BeforeEach methods.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Re: "Relative Execution Order of User Code and Extensions"

OK, so the indentation isn't mirroring the groupings of core test concepts: suite and test-class and test-method. My bad.

Re: My double quit()ting thing

I should trust that #742 is analogous to the TestRun scope I was talking about yesterday? Meaning I get a place to do WebDriver setup that is one-per-test-run and then at the end of the test run, quit WebDriver?

After that vanilla WebDriver accomplishment, I'll roll into a PicoContaier solution as I understand it, where I'd want to use a tree of containers instead of the tree of stores. Maybe it is as simple as putting containers in the store with names as keys (I'm using a type a key presently). Like so:

final parent = store.getOrComputeIfAbsent("AllCallback", container -> new DefaultPicoContainer())
return store.getOrComputeIfAbsent("EachCallback", container -> new DefaultPicoContainer(parent));

from junit5-samples.

marcphilipp avatar marcphilipp commented on September 26, 2024

I should trust that #742 is analogous to the TestRun scope I was talking about yesterday? Meaning I get a place to do WebDriver setup that is one-per-test-run and then at the end of the test run, quit WebDriver?

Yes, using the root extension context's store along with a way to "close" the objects stored in it will be a solution to this use case.

After that vanilla WebDriver accomplishment, I'll roll into a PicoContaier solution as I understand it, where I'd want to use a tree of containers instead of the tree of stores. Maybe it is as simple as putting containers in the store with names as keys (I'm using a type a key presently).

Sure, please give that a try and feel free to reach out to us for help/feedback.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Giving that a try - https://github.com/paul-hammant/junit5-maven-picocontainer - is as far as I can get with a simple demo. I don't know why JUnit isn't invoking BeforeTestExecutionCallback.beforeTestExecution() before the actual test method.

from junit5-samples.

marcphilipp avatar marcphilipp commented on September 26, 2024

It is invoked but too late for your constructor. If you switch to injecting the params to the test methods directly, it will work.

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

Can we have a new interface, BeforeTestInstantiationCallback then, please :)

I'll switch too, just because there's more for me to experience there. My preference as a test writer might be to have the Ctor handle injection because I have many methods that would need the field. That's case by case, of course.

from junit5-samples.

marcphilipp avatar marcphilipp commented on September 26, 2024

You can use the constructor as long as you don't rely on BeforeTestExecutionCallback to "fill your context". Is that the real use case? How would the test author configure it?

from junit5-samples.

paul-hammant avatar paul-hammant commented on September 26, 2024

As you suggest, into-test-method works with what I have: paul-hammant/junit5-maven-picocontainer@88f484d

To answer one of your questions .. my goal is to have:

  1. A root DI container (PicoContainer in my case, but it would be the same for Guice, etc) that has things that will be single-managed-instance for the entire test run. for those, I would want to be able to participate in the lifecycle. Specifically, tap into when the test run is finishing.

  2. A container at the scope of the test-method invocation, with a parent container of the one from #1. To be clear this container should be instantiated silently per test-method visitation, and any previous container at the same scope is eligible for GC (subject to how JUnit5 handles that). I also want to be able to participate in the lifecycle. Specifically, tap into when the test method invocation is finishing.

Ideally, I would have scopes between #1 and #2: Test-Instantiation, TestSuite, TestPackage, but y'all may not agree. The qualifying criteria for scopes would be that the items each leafwards 'contained' scope, cannot be in more than one container. A test method may have more than one tag, so it's ruled out of course. Y'all may tell me that suites are the same - test-methods can be in more than one suite.

from junit5-samples.

sbrannen avatar sbrannen commented on September 26, 2024

Hi guys,

This repo is only for sample projects and therefore not intended to be used for general discussions about JUnit Jupiter's programming and extension models.

So, let's move this discussion to a more appropriate forum where the general community can benefit.

I am therefore locking this (already closed) issue.

Not sure if I should make a new GH issue for this or not. Or go to a mail-list...

@paul-hammant, please create a new issue in the junit5 repo so that we can follow up there.

Thanks!

from junit5-samples.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.