In JUnit 5, how to run code before all tests

2020-02-10 11:36发布

问题:

The @BeforeAll annotation marks a method to run before all tests in a class.

http://junit.org/junit5/docs/current/user-guide/#writing-tests-annotations

But is there a way to run some code before all tests, in all classes?

I want to ensure that tests use a certain set of database connections, and the global one-time setup of these connections must occur before running any tests.

回答1:

Currently this is not supported, but there is a pull request for JUnit 5 regarding this topic: Introduce support for before/after callbacks once per entire test run.



回答2:

This is now possible in JUnit5 by creating a custom Extension, from which you can register a shutdown hook on the root test-context.

Your extension would look like this;

import org.junit.jupiter.api.extension.BeforeAllCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import static org.junit.jupiter.api.extension.ExtensionContext.Namespace.GLOBAL;

public class YourExtension implements BeforeAllCallback, ExtensionContext.Store.CloseableResource {

    private static boolean started = false;

    @Override
    public void beforeAll(ExtensionContext context) {
        if (!started) {
            started = true;
            // Your "before all tests" startup logic goes here
            // The following line registers a callback hook when the root test context is shut down
            context.getRoot().getStore(GLOBAL).put("any unique name", this);
        }
    }

    @Override
    public void close() {
        // Your "after all tests" logic goes here
    }
}

Then, any tests classes where you need this executed at least once, can be annotated with:

@ExtendWith({YourExtension.class})

When you use this extension on multiple classes, the startup and shutdown logic will only be invoked once.



回答3:

You can mark each of your test classes that uses your database with an interface that defines a static BeforeAll (so that it cannot be overridden). e.g.:

interface UsesDatabase {
    @BeforeAll
    static void initializeDatabaseConnections() {
        // initialize database connections
    }
}

This method will be invoked once for each implementing class so you will need to define a way to initialize your connections only once and then do nothing for the other calls.



回答4:

I am not aware of a mean to do that.

I would simply make sure that all code for @BeforeAll calls a certain singleton to make that init work (probably in a lazy way to avoid repetition).

Probably not convenient ... the only other option I see: I assume your tests run within a specific JVM job. You could hook an agent into that JVM run, that does that init work for you.

Beyond that: both suggestions sounds somehow like a hack to me. The real answer in my eyes: step back, and carefully examine your environment on its dependencies. And then find a way to prepare your environment in a way that your tests come up and the "right thing" happens automatically. In other words: consider looking into the architecture that bought you this problem.



回答5:

Above advises do not work for me, so I solved this problem like this:

Add to your Base abstract class (I mean abstract class where you initialize your driver in setUpDriver() method) this part of code:

private static boolean started = false;
static{
    if (!started) {
        started = true;
        try {
            setUpDriver();  //method where you initialize your driver
        } catch (MalformedURLException e) {
        }
    }
}

And now, if your test classes will extends from Base abstract class -> setUpDriver() method will be executed before first @Test only ONE time per project run.



标签: junit junit5