Comments (35)
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.
Cool. Presumably there will be community contributions of GuiceJunit5ParameterResolver
, DaggerJunit5ParameterResolver
and from me, PicoContainerJunit5ParameterResolver
from junit5-samples.
And PicoContainer team, seeing as I was on the committee too.
from junit5-samples.
@paul-hammant, are you planning on creating a PR for such examples?
If not, what exactly are you proposing/asking?
from junit5-samples.
FWIW, the MockitoExtension
and SpringExtension
both demonstrate how to make use of DI in JUnit Jupiter tests.
from junit5-samples.
My bad, I didn't look into MockitoExtension or SpringExtension.
from junit5-samples.
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.
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.
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.
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.
https://github.com/junit-pioneer/junit-pioneer might be good place to host some of them.
from junit5-samples.
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.
You can see some basic examples of DI support with the SpringExtension
in Spring's test suite here:
- SpringExtensionTestCase
- SpringJUnit5AutowiredConstructorInjectionTestCase
- SpringJUnit5ConstructorInjectionTestCase
Hope that gives you an idea of the power of DI in JUnit Jupiter! 😎
from junit5-samples.
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.
@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.
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.
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.
- for constructor injection, lifecycle method injection, and test method injection, one has to implement
ParameterResolver
and provide individual parameters that way. - 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.
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:
- Applications
- Session
- Request
I'd imagine for test execution (different to web application), there would be:
- Run
- Suite
- Package
- TestClass
- 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.
@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.
Btw, I filed junit-team/junit5#925 for retrieving the root extension context more conveniently.
from junit5-samples.
Regarding the "lifecycle of scopes" you may hook into the following extension points:
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.
@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.
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.
Comments in the pull-request, @marcphilipp
from junit5-samples.
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.
I've pushed a new commit to my nascent WebDriver example repo. I have implemented the
AfterAllCallback
interface on the WebDriverExtension class. However, theafterAll(..)
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 expectAfterEachCallback
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.
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.
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.
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.
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.
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.
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.
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.
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:
-
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.
-
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.
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)
- Use Apache Ivy in Ant examples
- Upgradle to 6.0.1 HOT 1
- Help | Is there any way I can add @Tags dynamically from Test Data HOT 1
- Use HTTPS
- Add Android integration test project HOT 1
- Issue with JUnit after finishing Level 10 HOT 1
- build.gradle.kts and tag expressions HOT 2
- How to use TestPipeline HOT 3
- 5.7.1 breaks Bazel sample HOT 13
- DisplayName tag not working properly in junit5-jupiter-starter-maven example HOT 4
- Add module-info.java to have an example that works with Jigsaw too. HOT 1
- Add `junit5-jupiter-starter-graalvm-native` sample HOT 5
- Build broken when using kotlin 1.7.21 HOT 4
- Update used GitHub Actions HOT 2
- Bazel starter issues a deprecation warning HOT 4
- junit5-jupiter-starter-gradle @ParameterizedTest name not displaying HOT 2
- Groovy HOT 2
- Intellij IDE shows 'no name' for a kotlin-bazel project HOT 1
- Adopt Renovate
- Dependency Dashboard
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from junit5-samples.