I am testing a Verticle which, while simple, can sometimes make a long-running network request at startup. As well, this network request counts against API limits, so I'd like to reduce the amount of times the startup procedure is called given that most of my tests don't care about this but require it to have run.
The correct behavior would be some sort of @BeforeAll annotation in JUnit5 but that runs only once for the ENTIRE run. There has been a lot of hand-wringing about this on junit5 github issues because, while you CAN achieve this effect, its not well documented. (c.f.: https://github.com/junit-team/junit5/issues/1555 )
private static Store rootStore(ExtensionContext extensionContext) {
return extensionContext.getRoot().getStore(Namespace.GLOBAL);
}
The difference is that if you attach a parameter to THAT store, it will not get closed until ALL of the tests are complete. This allows you to do something like this to, in effect, create a @BeforeEntireSuite.
By way of example I've created this little helper class that would be much better off inside vertx-junit5 as some convenience to get access to a Vertx that will not be torn down between sets of tests. You can extend this class and provide a keyname method and a method that starts the vertx/verticle pair as implementations of the abstract methods. Obviously there are a lot of kludges here, but i think it's a complete example.
package ai.brace.util;
import io.vertx.core.Vertx;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import static org.assertj.core.api.Assertions.assertThat;
abstract class GlobalVerticleExtension
implements BeforeAllCallback, ExtensionContext.Store.CloseableResource, AutoCloseable
{
private static final ExtensionContext.Namespace NAMESPACE = ExtensionContext.Namespace.GLOBAL;
private static final Logger logger = LogManager.getLogger();
/**
* The verticle's vertx context.
*/
private Vertx vertx = null;
/**
* Return a unique name for this verticle so that we can potentially register multiple with junit
*
* @return A unique name for the verticle.
*/
public abstract String getName();
/**
* Start the verticle and give back a vertx instance that will be shut down at the end.
*
* @param future complete or cancel this depending on how the startup procedure goes.
*/
public abstract void start(final CompletableFuture<Vertx> future);
private GlobalVerticleExtension create(final String name)
{
final CompletableFuture<Vertx> future = new CompletableFuture<>();
logger.info("Starting " + name);
start(future);
try
{
vertx = future.get(10, TimeUnit.SECONDS);
}
catch (InterruptedException e)
{
throw new RuntimeException(name + " startup interrupted.");
}
catch (ExecutionException e)
{
throw new RuntimeException(e);
}
catch (TimeoutException e)
{
throw new RuntimeException(name + " startup timeout.");
}
assertThat(vertx).isNotNull();
assertThat(vertx.deploymentIDs()).isNotEmpty().hasSize(1);
logger.info(name + " startup complete.");
return this;
}
@Override
public void beforeAll(ExtensionContext context)
{
// N.B.: If you don't include getRoot() here this will become a beforeAll / close handler for every annotated
// test class. This might actually be very useful, but for this class we want code to exec once per test run.
context.getRoot().getStore(NAMESPACE).getOrComputeIfAbsent(getName() + "_global_verticle", this::create);
}
@Override
public void close()
{
if (vertx == null)
{
return;
}
final CompletableFuture<Boolean> future = new CompletableFuture<>();
vertx.close(result -> future.complete(result.succeeded()));
try
{
future.get(10, TimeUnit.SECONDS);
}
catch (InterruptedException | ExecutionException | TimeoutException e)
{
// This is an exit routine, so log then eat the exception.
logger.error(e);
}
vertx = null;
}
}
I am testing a Verticle which, while simple, can sometimes make a long-running network request at startup. As well, this network request counts against API limits, so I'd like to reduce the amount of times the startup procedure is called given that most of my tests don't care about this but require it to have run.
The correct behavior would be some sort of @BeforeAll annotation in JUnit5 but that runs only once for the ENTIRE run. There has been a lot of hand-wringing about this on junit5 github issues because, while you CAN achieve this effect, its not well documented. (c.f.: https://github.com/junit-team/junit5/issues/1555 )
For vertx-junit, what we need is the ability to add a parameter to the root extension context, instead of the one passed directly by junit5. Specifically, this line https://github.com/vert-x3/vertx-junit5/blob/master/vertx-junit5/src/main/java/io/vertx/junit5/VertxExtension.java#L162, but
The difference is that if you attach a parameter to THAT store, it will not get closed until ALL of the tests are complete. This allows you to do something like this to, in effect, create a
@BeforeEntireSuite
.By way of example I've created this little helper class that would be much better off inside vertx-junit5 as some convenience to get access to a Vertx that will not be torn down between sets of tests. You can extend this class and provide a keyname method and a method that starts the vertx/verticle pair as implementations of the abstract methods. Obviously there are a lot of kludges here, but i think it's a complete example.
Note: the example at https://github.com/vert-x3/vertx-junit5/blob/master/vertx-junit5/src/test/java/io/vertx/junit5/OtherExtension.java#L38 does not quite apply because the function it's calling in
VertxExtension.java
doesn't give access tocontext.getRoot()
.