问题
To create the environment just once and to avoid inheritance I have defined a JUnit Suite class with a @ClassRule
:
@RunWith(Suite.class)
@Suite.SuiteClasses({
SuiteTest1.class
})
public class JUnitTest {
@ClassRule
private static DockerComposeContainer env = ...
@BeforeClass
public static void init(){
...
}
...
}
And there's a Test class that uses env in a test method:
public class SuiteTest1 {
@Test
public void method(){
client.query(...);// Executes a query against docker container
}
}
When I execute the tests by running the Test Suite everything works as expected. But when I directly try to run (even with IDE) the SuiteTest1
test class, it fails and nothing from the Suite is called (i.e. @
ClassRule and @BeforeClass
).
Any suggestions on how to achieve also the SuiteTest1 single execution in an good way (without calling static methods of JUnitTest
from within the SuiteTest1
) ?
回答1:
Rephrasing the question: you want a JUnit suite with before-all and after-all hooks, which would also run when running the tests one by one (e.g. from an IDE).
AFAIK JUnit 4 provides nothing out-of-the-box for this, but if you're OK with incorporating some Spring third-parties deps (spring-test and spring-context) into your project I can propose a workaround I've been using.
The full example of what is described in this question can be found here.
Solution (using Spring)
We'll use Spring context for implementing our initialization and cleanup. Let's add a base class for our tests:
@ContextConfiguration(initializers = AbstractTestClass.ContextInitializer.class)
public class AbstractTestClass {
@ClassRule
public final static SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
public static class ContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
System.out.println("Initializing context");
context.addApplicationListener(
(ApplicationListener<ContextClosedEvent>)
contextClosedEvent ->
System.out.println("Closing context"));
}
}
}
Note the SpringClassRule and SpringMethodRule JUnit rules which enhance our base class with Spring-superpowers (Spring test annotation processing - ContextConfiguration in this case, but there are many more goodies in there - see Spring testing reference for details). You could use SpringRunner for this purpose, but it's a far less flexible solution (thus omitted).
Test classes:
public class TestClass1 extends AbstractTestClass {
@Test
public void test() {
System.out.println("TestClass1 test");
}
}
public class TestClass2 extends AbstractTestClass {
@Test
public void test() {
System.out.println("TestClass2 test");
}
}
And the test suite:
@RunWith(Suite.class)
@SuiteClasses({TestClass1.class, TestClass2.class})
public class TestSuite {
}
Output when running the suite (removed Spring-specific logs for brievity):
Initializing context
TestClass1 test
TestClass2 test
Closing context
Output when running a single test (TestClass1
):
Initializing context
TestClass1 test
Closing context
A word of explanation
The way this works is because of Spring's context caching. Quote from the docs:
Once the TestContext framework loads an
ApplicationContext
(orWebApplicationContext
) for a test, that context is cached and reused for all subsequent tests that declare the same unique context configuration within the same test suite. To understand how caching works, it is important to understand what is meant by “unique” and “test suite.”-- https://docs.spring.io/spring/docs/5.1.2.RELEASE/spring-framework-reference/testing.html#testcontext-ctx-management-caching
Beware that you will get another context (and another initialization) if you override the context configuration (e.g. add another context initializer with ContextConfiguration
) for any of the classes in the hierarchy (TestClass1
or TestClass2
in our example).
Using beans to share instances
You can define beans in your context. They'll be shared across all tests using the same context. This can be useful for sharing an object across the test suite (a Testcontainers container in your case judging by the tags).
Let's add a bean:
@ContextConfiguration(initializers = AbstractTestClass.ContextInitializer.class)
public class AbstractTestClass {
@ClassRule
public final static SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
public static class ContextInitializer
implements ApplicationContextInitializer<ConfigurableApplicationContext> {
@Override
public void initialize(ConfigurableApplicationContext context) {
ADockerContainer aDockerContainer = new ADockerContainer();
aDockerContainer.start();
context.getBeanFactory().registerResolvableDependency(
ADockerContainer.class, aDockerContainer);
context.addApplicationListener(
(ApplicationListener<ContextClosedEvent>)
contextClosedEvent ->
aDockerContainer.stop());
}
}
}
And inject it into the test classes:
public class TestClass1 extends AbstractTestClass {
@Autowired
private ADockerContainer aDockerContainer;
@Test
public void test() {
System.out.println("TestClass1 test " + aDockerContainer.getData());
}
}
public class TestClass2 extends AbstractTestClass {
@Autowired
private ADockerContainer aDockerContainer;
@Test
public void test() {
System.out.println("TestClass2 test " + aDockerContainer.getData());
}
}
ADockerContainer
class:
public class ADockerContainer {
private UUID data;
public void start() {
System.out.println("Start container");
data = UUID.randomUUID();
}
public void stop() {
System.out.println("Stop container");
}
public String getData() {
return data.toString();
}
}
(Example) output:
Start container
TestClass1 test 56ead80b-ec34-4dd6-9c0d-d6f07a4eb0d8
TestClass2 test 56ead80b-ec34-4dd6-9c0d-d6f07a4eb0d8
Stop container
来源:https://stackoverflow.com/questions/52551735/running-a-single-test-from-a-suite-with-classrule-fails