I have solved this problem a little bit using a class rule. Let's say that we wanted our cucumber test to start a single TestContainers container that we were testing. Let's say we were testing REDIS (we're not but it's a simple example).
@RunWith(Cucumber.class)
@CucumberOptions(...)
public class TestRunner {
@ClassRule
static GenericContainer REDIS = new GenericContainer<>("redis:5.0.3-alpine")
.withExposedPorts(6379);
// obviously we weren't testing redis, but this gives you the idea of a container
}
The above causes the TestContainers class GenericContainer to be initialized before the cucumber lifecycle and then torn down afterwards. You can write your own custom JUnit rule, extending TestRule and use it to decorate the execution of tests with your own custom set up.
A common problem with this is that you have to somehow get access to the objects created at this point in the lifecycle from things created on a per-scenario basis. However, given that this lifecycle is part of the static state of the Cucumber test suite, you can just access the test rule object via the static field of the suite.
I have a blog post on this here - https://codingcraftsman.wordpress.com/2020/01/20/extending-the-cucumber-test-lifecycle/