junit5
junit5 copied to clipboard
Inject into test case from launcher
I would like to inject a test class’ field from my launcher before executing the test. IIUC, this is currently impossible. This feature request documents the reason I (think that I) need this.
Overview
I develop a software to grade student’s work. The teacher would write a unit test then run my software. My software would download student 1’s work, compile it on the fly, execute the teacher test on the student 1 work, grade it; download student 2’s work, compile it on the fly, execute the teacher test on the student 2 work, grade it; and so on for each students; and then return a list of all grades to the user.
More details
The teacher would ask students to implement an interface that she provides, say, Additioner
.
The teacher would write a unit test class that has a field Additioner studentAdditioner
and test methods that use that field.
My software compiles student 1’s class, say, MyAdditioner
, that implements Additioner
, and should inject an instance of that class into the field studentAdditioner
so that the teacher unit test will run on the student 1 instance.
Missing
My software is able to use JUnit’s API to launch the teacher test, but is unable to inject the student instance into the test field. Note that such injection must happen in the context that creates the launcher, not in the context of the junit test, as the test is started by my software.
Workaround
As suggested in the linked threads, one could use JNDI or other external means of communicating an instance, but this would introduce unnecessary complexity and probably be non-transparent to my users (i.e., the teacher).
Sorry if I misunderstood the discussions of the linked threads and it is actually possible, in which case I’d be glad to be directed to some way of proceeding.
You're right in your assessment that it's currently not possible to inject arbitrary objects via the Launcher for injection into test class instances.
As a workaround, your code could instantiate MyAdditioner
and store it in a ThreadLocal
. A custom, globally registered Jupiter extension (for example, a TestInstancePostProcessor
) could then read the ThreadLocal
and inject it into a field of the test class. Would that be feasible in your case?
As a workaround, your code could instantiate
MyAdditioner
and store it in aThreadLocal
. A custom, globally registered Jupiter extension (for example, aTestInstancePostProcessor
) could then read theThreadLocal
and inject it into a field of the test class. Would that be feasible in your case?
I believe that this would be feasible, thank you for the suggestion. Though it does feel hackish to use this sort of “global variable” mechanism to pass data around (but it is still way better than the JNDI workaround that I was considering). It also creates potential security issues because the student code itself can access the ThreadLocal. So, for example, if in the future I want to also inject a “correct” (teacher provided) instance MyCorrectAdditioner
using the same mechanism, thinking must happen to make sure that the student code is not able to change the thread local variables at some point during the grading process. (I am not claiming that this would be impossible to guarantee, just that it does introduce further things to wonder about.) So I would be happy if a cleaner solution would come to exist, where only my correcting code and the teacher unit test have access to the instances that get injected.
Unrelatedly: should this feature request really be marked as component: Jupiter
? It feels like a limitation of the launcher itself.
Unrelatedly: should this feature request really be marked as
component: Jupiter
? It feels like a limitation of the launcher itself.
I was thinking about Jupiter extensions being able to access it but you're correct to point out that it needs a Platform mechanism as well.
My software is able to use JUnit’s API to launch the teacher test, but is unable to inject the student instance into the test field. Note that such injection must happen in the context that creates the launcher, not in the context of the junit test, as the test is started by my software.
Maybe I'm missing something, but I think I'd do the following...
- Supply the fully qualified class name of the student's implementation as a JUnit Platform configuration parameter.
- Write a Jupiter
Extension
that reads the configuration parameter, uses reflection to instantiate the implementation, and injects the instance into a field in the test class. - Perform tests on that instance.
Wouldn't that solve your issue?
Wouldn't that solve your issue?
My software downloads the code, compiles it, possibly runs other checks or does other non trivial things (such as deciding which version to compile and test, depending on deadlines and requirements), and it feels unnatural to me to hand control at that point to a Jupiter extension that would then instantiate. The instantiation logic itself is non trivial and I’d rather not have that started as part of a Jupiter extension. In particular, the code that gets compiled on the fly may be in some temporary file space (or even hopefully in memory only in some future version), with the testing sub-process having reduced access to the disk.
To put it otherwise, the proposed approach requires in a sense to invert the flow of control, meaning, let Jupiter start and control a bigger part of the testing process, rather than my software having responsibility for what concerns the student code preparation and instantiation.
If you would like us to be able to process this issue, please provide the requested information. If the information is not provided within the next 3 weeks, we will be unable to proceed and this issue will be closed.
I believe that I have provided the requested information. Otherwise, please tell me what is missing. Just replying that I haven’t provided the requested information is rude and unhelpful.
Related issue:
- #2816
Hi, I'm running into this issue as well. I have dependencies in my test that use @Inject. I'm using the platform launcher api to discover and run these tests. But, my DI library(guice) is currently unable to inject deps into them. I'm not sure why. But, if there was a way to programmatically "provide" extensions like the @ExtendsWith, this might be possible.
@PinkFloyded Have you seen @RegisterExtension
for programmatic extension registration?