In order to speed up a large integration test (@SpringBootTest
), the test class was changed to use @TestInstance(TestInstance.Lifecycle.PER_CLASS)
and @Before-/AfterAll
. The problem with this is that it is no longer possible to run this test class separately anymore, because the application won't start. I am trying to identify and fix the problem.
It seems like the TestInstance annotation somehow causes the @Testcontainers
not to be called early enough in the lifecycle to be initialized before the application starts. In other words, if a different test class runs before this one, then the container is started, then the application starts, and then the tests are run (See the testcontainers doc on details for containers shared by test classes). However, if no other test class runs, then it appears that the application tries to start (fails), then the container would be started, and then the tests would be run?
Works, ie container is started, and then the application loads, and then the "test" is run:
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { Main.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
//@TestInstance(TestInstance.Lifecycle.PER_CLASS) <-----
class TestInstanceTesting {
@Container
static final MariaDBContainerSingleton CONTAINER = MariaDBContainerSingleton.getInstance();
@Test
void test() {}
}
Fails, ie there is no mention of the container starting in the logs, the application tries to start, and then fails...
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = { Main.class }, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS) // <-----
class TestInstanceTesting {
@Container
static final MariaDBContainerSingleton CONTAINER = MariaDBContainerSingleton.getInstance();
@Test
void test() {}
}
...with this message:
... [org.apache.tomcat.jdbc.pool.DataSource]: Factory method 'dataSource' threw exception; nested exception is org.springframework.boot.autoconfigure.jdbc.DataSourceProperties$DataSourceBeanCreationException: Failed to determine a suitable driver class []
I tried to reproduce the issue in a new, simple project. I created a small entity, with a repository, service and controller. I then created an integration test similar to that of the real project. To my surprise, it works here:
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@Testcontainers
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class IntegrationTest {
@Container
public static final MariaDBContainerSingleton CONTAINER = MariaDBContainerSingleton.getInstance();
/* some integration tests */
}
This indicates to me that some configuration from @SpringBootTest(classes = { Main.class } ...)
, or some application properties is causing this failure. None of the configuration options for Testcontainers seems related to the lifecycle (doc), and I couldn't find any testcontainers config in the project. I also could not find any mention of Jupiter PER_CLASS config, and again, didn't see anything like that in the project config.
What configuration or application properties could cause this to fail in the real project?
(I was able to find this issue, which seems to be exactly the same problem, but unfortunately this thread did not have a solution, and I could not find the user on SO, or a question about this specific issue)
Thank you @Kevin Wittek, @DynamicPropertySource worked.
Update: this isn't working for me after all, see Post-Post-Answer Update
// Update: NOT WORKING!
@DynamicPropertySource
public static void propertySource(final DynamicPropertyRegistry dynamicPropertyRegistry) {
if (!CONTAINER.isRunning()) {
LOGGER.warn("Test container not started by Testcontainers, starting manually.");
CONTAINER.start();
} else {
LOGGER.debug("Test container started by Testcontainers, as desired.");
}
}
Okay, it seems that I was too quick to close the question, as @DynamicPropertySource
is not playing nice with other tests. Fortunately, it did point me in the right direction, and the solution was actually even simpler, I just had to change the method to be the test class constructor:
public IntegrationTest() {
if (!CONTAINER.isRunning()) {
LOGGER.warn("Test container not started by Testcontainers, starting manually.");
CONTAINER.start();
} else {
LOGGER.debug("Test container started by Testcontainers, as desired.");
}
}
If you are lacking control over the order and interactions between the JUnit-Jupiter and the Spring Boot lifecycle, I would suggest not using the Testcontainers JUnit-Jupiter extension, but instead, starting the containers by hand using container.start()
.
You can combine this approach with a @DynamicProperySource
annotated method, in which the container is started:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/test/context/DynamicPropertySource.html
However, if you will this is an issue with either SpringBootTest, JUnit-Jupiter, or the Testcontainers Jupiter-Extension, I'd suggest to create issues in the corresponding projects.