Document how to use `CloseableResource`
Overview
As discussed in the Gitter channel, users would greatly benefit from detailed documentation and examples regarding how to make use of CloseableResource in the ExtentionContext.Store.
Deliverables
- [ ] Document how to use
CloseableResourcein various scenarios, potentially via examples in thejunit5-samplesrepository.
This is my use case: create html report object before all tests and write to file after all tests executed:
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
public class AfterTestExecutionHook implements
BeforeAllCallback {
@Override
public void beforeAll(ExtensionContext context) throws Exception {
//initialize "after all test run hook"
context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL).put("my_report", new CloseableOnlyOnceResource());
}
private static class CloseableOnlyOnceResource implements
ExtensionContext.Store.CloseableResource {
//ReportManager contains static field of MyReport type as singleton storage and access point
private static MyReport htmlReport = ReportManager.createReportSingleton();
@Override
public void close() {
//After all tests run hook.
//Any additional desired action goes here
htmlReport.write();
}
}
}
usage:
@ExtendWith(AfterTestExecutionHook .class)
class AnotherClassDemo {
Here is also a unit test showing a global resource:
https://github.com/junit-team/junit5/blob/548de1f589b2609b14e8af05e1a356d3a7aa6099/platform-tests/src/test/java/org/junit/jupiter/extensions/Heavyweight.java#L26-L92
It is also a parameter resolver, so it can be injected into test methods:
https://github.com/junit-team/junit5/blob/548de1f589b2609b14e8af05e1a356d3a7aa6099/platform-tests/src/test/java/org/junit/jupiter/extensions/HeavyweightAlphaTests.java#L27-L64
Mind the @ResourceLock(Heavyweight.Resource.ID) annotation. Global resources need synchronization guarding when Jupiter executes tests in parallel: https://junit.org/junit5/docs/snapshot/user-guide/#writing-tests-parallel-execution
Thanks for providing the examples, @sormuras.
With regard to the last point, @ResourceLock is unnecessary if the CloseableResource is itself thread-safe and designed for concurrent access -- right?
With regard to the last point,
@ResourceLockis unnecessary if theCloseableResourceis itself thread-safe and designed for concurrent access -- right?
Absolutely! I'm preparing a new default extension which will provide a @Singleton annotation to support exactly those use cases.
That code snippet is invalid. As it is, that code is executed after every test class. The correct way is to use root context:
context.getRoot().getStore(ExtensionContext.Namespace.GLOBAL).put("my_report", new CloseableOnlyOnceResource());
Anyway, thanks for it. Solved my issue after a bit of digging!
@Tofel I find the statement "invalid" inappropriate to working code. I agree that it's kind of disadvantage that it's executed for each test class, though I did "lazy initialization" ex:
if (my_report == null) {
//perfom init here
}
return my_report
and I don't care about executing per class anymore
@vyarosh don't get me wrong, your snipped helped me to solve the issue, so props for that. But as it is it does not work as expected (executed only 1 after all tests) in case, when you have more than 1 test class. And if you don't have more than 1 test class afterAll would the the trick way better.
As I said, thanks for the snippet! I really hope that in jUnit 5.5 we will see a real deal before/afterSuite.
@Tofel no worries, thank you for reply. I really missed the point that it's executed for class. And yeah surfed really a lot before found working solution and waiting for before/afterSuite(fingers-crossed) as well.
@sbrannen this one isn't up-for-grabs any more and is resolved in Junit 5.5 (reference javadoc) and via example. Is that right?
This issue is still up-for-grabs, because the example you're referring to is only used for internal tests. To resolve this issue, the user guide should show or list such an example, but probably a more realistic one. Maybe a com.sun.net.httpserver.HttpServer?
This issue has been automatically marked as stale because it has not had recent activity. Given the limited bandwidth of the team, it will be automatically closed if no further activity occurs. Thank you for your contribution.
It sounds like this issue should be kept open, reading the OP.
This issue has been automatically marked as stale because it has not had recent activity. Given the limited bandwidth of the team, it will be automatically closed if no further activity occurs. Thank you for your contribution.
Let's keep this one open since it's a recurring topic.
This issue is still up-for-grabs, because the example you're referring to is only used for internal tests. To resolve this issue, the user guide should show or list such an example, but probably a more realistic one. Maybe a
com.sun.net.httpserver.HttpServer?
@marcphilipp Is this the type of example you had in mind, or should it be a bit more detailed? This is my first contribution, so I appreciate any feedback.
import com.sun.net.httpserver.HttpServer;
import org.junit.jupiter.api.extension.ExtensionContext.Store.CloseableResource;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
/**
* Demonstrates an implementation of {@link CloseableResource} using an {@link HttpServer}.
*/
public class HttpServerResource implements CloseableResource {
private final HttpServer httpServer;
/**
* Initializes the Http server resource, using the given port.
*
* @param port (int) The port number for the server, must be in the range 0-65535.
* @throws IOException if an IOException occurs during initialization.
*/
public HttpServerResource(int port) throws IOException {
this.httpServer = HttpServer.create(new InetSocketAddress(port), 0);
}
/**
* Starts the Http server with an example handler.
*/
public void start() {
//Example handler
httpServer.createContext("/example", exchange -> {
String test = "This is a test.";
exchange.sendResponseHeaders(200, test.length());
try (OutputStream os = exchange.getResponseBody()) {
os.write(test.getBytes());
}
});
httpServer.setExecutor(null);
httpServer.start();
}
@Override
public void close() throws Throwable {
httpServer.stop(0);
}
}
@SveinKare Yes, plus a discussion in which ExtensionContext such a resource should be stored. For example, creating it lazily using Store.getOrComputeIfAbsent and storing it in the root context, would ensure it's created at most once per execution.