junit5 icon indicating copy to clipboard operation
junit5 copied to clipboard

Document how to use `CloseableResource`

Open sbrannen opened this issue 7 years ago • 17 comments

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 CloseableResource in various scenarios, potentially via examples in the junit5-samples repository.

sbrannen avatar Aug 19 '18 15:08 sbrannen

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 {

vyarosh avatar Aug 20 '18 09:08 vyarosh

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

sormuras avatar Aug 21 '18 07:08 sormuras

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?

sbrannen avatar Aug 21 '18 10:08 sbrannen

With regard to the last point, @ResourceLock is unnecessary if the CloseableResource is 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.

sormuras avatar Nov 09 '18 08:11 sormuras

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 avatar Apr 25 '19 15:04 Tofel

@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 avatar Apr 26 '19 14:04 vyarosh

@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 avatar Apr 26 '19 15:04 Tofel

@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.

vyarosh avatar Apr 26 '19 18:04 vyarosh

@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?

gokareless avatar Apr 06 '20 11:04 gokareless

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 avatar Apr 06 '20 13:04 marcphilipp

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.

stale[bot] avatar May 13 '21 18:05 stale[bot]

It sounds like this issue should be kept open, reading the OP.

jbduncan avatar May 13 '21 20:05 jbduncan

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.

stale[bot] avatar Jun 19 '22 20:06 stale[bot]

Let's keep this one open since it's a recurring topic.

sbrannen avatar Jun 20 '22 18:06 sbrannen

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 avatar Jan 20 '24 11:01 SveinKare

@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.

marcphilipp avatar Apr 15 '24 08:04 marcphilipp