How to reuse TestContainer ? (Junit 4) - java

Hello everyone :) i have 3 questions :
How Reuse TestContainer with Junit 4 ?
How i can verify the amount of containers use during my test ?
By default a new container started foreach #Test or for whole class ?
Thank you in advance for your answers
PostgresTestContainer.java
#ContextConfiguration(initializers = PostgresTestContainer.Initializer.class)
public abstract class PostgresTestContainer {
#ClassRule
public static PostgreSQLContainer postgresContainer = new PostgreSQLContainer(TCConfig.POSTGRESQL_VERSION.toString())
.withDatabaseName(TCConfig.TC_DBNAME)
.withUsername(TCConfig.TC_USERNAME)
.withPassword(TCConfig.TC_PASSWORD);
public static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
private static String stringConnection = postgresContainer.getJdbcUrl();
#Override
public void initialize(ConfigurableApplicationContext applicationContext) {
TestPropertyValues values = TestPropertyValues.of(
"spring.datasource.url=" + stringConnection,
"spring.datasource.username=" + TCConfig.TC_USERNAME,
"spring.datasource.password=" + TCConfig.TC_PASSWORD
);
values.applyTo(applicationContext);
}
}
}
PostgreSQL12Test.java
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class PostgreSQL12_Test extends PostgresTestContainer {
#Autowired
private MemberService memberService;
#Autowired
private Flyway flyway;
#Before
public void initialize() {
flyway.migrate();
}
#Test
public void shoudRunPostgreSQLContainer() throws Exception {
Connection connection = DriverManager.getConnection(postgresContainer.getJdbcUrl(), postgresContainer.getUsername(), postgresContainer.getPassword());
ResultSet resultSet = connection.createStatement().executeQuery("SELECT 666");
resultSet.next();
int result = resultSet.getInt(1);
assertThat(result).isEqualByComparingTo(666);
}
}
VERSIONS
TestContainers - Postgresql : 1.13.0
Spring Boot : 2.0.0 ( Junit 4 )
Docker : 19.03.11
Os : 20.04.1 LTS (Focal Fossa)

How Reuse TestContainer with Junit 4?
It should already work the way you wrote your test. You have the
container annotated with #ClassRule so it should only be loaded once.
How i can verify the amount of containers use during my test?
Put a breakpoint in your test method and run docker ps in a terminal.
By default a new container started foreach #Test or for whole class?
With #ClassRule it should be created for the class. You can just remove
that annotation and then the lifecycle of the container will be managed
by java itself (once if the field is static and for every test method
if it's not)

To reuse Container for all test class just use static without #ClassRule or #Rule.
public class PostgresTestContainer {
public static final PostgreSQLContainer POSTGRESQL_CONTAINER = new PostgreSQLContainer<>(DockerImageName.parse("postgres:9.6.12"))
.withDatabaseName("db_name")
.withUsername("db_user")
.withPassword("db_pass");
static {
POSTGRE_SQL_CONTAINER.start();
}
}

Related

ApplicationContext for Test environment using TestContainer in Micronaut application not working

I am trying to test the Micronaut application using MongoDb and RabbitMQ test containers. The application.yml has the below configuration
mongodb:
uri: "mongodb://${MONGO_HOST:localhost}:${MONGO_PORT:27017}"
database: "FeteBird-Product"
I have the below code for the configuration
#Introspected
#ConfigurationProperties("mongodb")
public record MongodbConfiguration(#NotNull String uri, #NotNull String database) {
}
In the repository. The repository is in another project
#Singleton
public record Repository(MongoClient mongoClient, MongodbConfiguration mongodbConfiguration) implements IRepository {
#Override
public <T> MongoCollection<T> getCollection(String collectionName, Class<T> typeParameterClass) {
return mongoClient
.getDatabase(mongodbConfiguration.database())
.getCollection(collectionName, typeParameterClass);
}
}
mongodbConfiguration.uri is always mongodb://localhost:27017, however in the JUnit testing I have the below code
#Testcontainers
public abstract class TestContainerFixture {
public static final GenericContainer mongoDBContainer;
public static final GenericContainer rabbitMQContainer;
static {
mongoDBContainer = new GenericContainer(DockerImageName.parse("mongo:4.0.10")).withExposedPorts(27017);
rabbitMQContainer = new GenericContainer(DockerImageName.parse("rabbitmq:3-management-alpine")).withExposedPorts(5672);
mongoDBContainer.start();
rabbitMQContainer.start();
}
}
#MicronautTest
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class DiscountDeleteListenerTest extends TestContainerFixture {
private ApplicationContext applicationContext;
#BeforeAll
#DisplayName("Initial application setup")
void initialApplicationSetup() {
applicationContext = ApplicationContext.run(
Map.of("mongodb.uri",
String.format("mongodb://%s:%s", mongoDBContainer.getContainerIpAddress(), mongoDBContainer.getMappedPort(27017)),
"rabbitmq.uri",
String.format("amqp://%s:%s", rabbitMQContainer.getContainerIpAddress(), rabbitMQContainer.getMappedPort(5672)))
, "test"
);
iDiscountProducer = applicationContext.getBean(IDiscountProducer.class);
}
}
The below code is fine
String.format("mongodb://%s:%s", mongoDBContainer.getContainerIpAddress(), mongoDBContainer.getMappedPort(27017))
This gives the URL as mongodb://localhost:57032
Now when I finish all the unit tests and check the database it is inserting, updating, and deleting from the local docker instance that is port 27017
The host for the cluster is still pointing to 27017
Got some idea from here https://github.com/micronaut-projects/micronaut-test/issues/32 but still quite not sure how to do it.
we cant use MicronautTest and TestContainer at the same time. When you annotate the test with MicronautTest it wake up the application before to call your initialization method
So removing the #MicronautTest solve the issue

How to reuse Testcontainers between multiple SpringBootTests?

I'm using TestContainers with Spring Boot to run unit tests for repositories like this:
#Testcontainers
#ExtendWith(SpringExtension.class)
#ActiveProfiles("itest")
#SpringBootTest(classes = RouteTestingCheapRouteDetector.class)
#ContextConfiguration(initializers = AlwaysFailingRouteRepositoryShould.Initializer.class)
#TestExecutionListeners(listeners = DependencyInjectionTestExecutionListener.class)
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Tag("docker")
#Tag("database")
class AlwaysFailingRouteRepositoryShould {
#SuppressWarnings("rawtypes")
#Container
private static final PostgreSQLContainer database =
new PostgreSQLContainer("postgres:9.6")
.withDatabaseName("database")
.withUsername("postgres")
.withPassword("postgres");
But now I have 14 of these tests and every time a test is run a new instance of Postgres is spun up. Is it possible to reuse the same instance across all tests? The Singleton pattern doesn't help since every test starts a new application.
I've also tried testcontainers.reuse.enable=true in .testcontainers.properties and .withReuse(true), but that didn't help.
You can't use the JUnit Jupiter annotation #Container if you want to have reusable containers. This annotation ensures to stop the container after each test.
What you need is the singleton container approach, and use e.g. #BeforeAll to start your containers. Even though you then have .start() in multiple tests, Testcontainers won't start a new container if you opted-in for reusability using both .withReuse(true) on your container definition AND the following .testcontainers.properties file in your home directory:
testcontainers.reuse.enable=true
A simple example might look like the following:
#SpringBootTest
public class SomeIT {
public static GenericContainer postgreSQLContainer = new PostgreSQLContainer().
withReuse(true);
#BeforeAll
public static void beforeAll() {
postgreSQLContainer.start();
}
#Test
public void test() {
}
}
and another integration test:
#SpringBootTest
public class SecondIT {
public static GenericContainer postgreSQLContainer = new PostgreSQLContainer().
withReuse(true);
#BeforeAll
public static void beforeAll() {
postgreSQLContainer.start();
}
#Test
public void secondTest() {
}
}
There is currently a PR that adds documentation about this
I've put together a blog post explaining how to reuse containers with Testcontainers in detail.
If you decide go forward with the singleton pattern, mind the warning in "Database containers launched via JDBC URL scheme". I took hours till I note that, even though I was using the singleton pattern, an additional container was always being created mapped on a different port.
In summary, do not use the test containers JDBC (host-less) URIs, such as jdbc:tc:postgresql:<image-tag>:///<databasename>, if you need use the singleton pattern.
Accepted answer is great but the problem is you still have to repeat the configurations(creating, starting and etc.) for each integration tests. It would be better to have simpler configuration with fewer lines of code. I think cleaner version would be using JUnit 5 extensions.
This is how I solved the problem. Below sample uses MariaDB container but the concept is applicable to all.
Create the container config holding class:
public class AppMariaDBContainer extends MariaDBContainer<AppMariaDBContainer> {
private static final String IMAGE_VERSION = "mariadb:10.5";
private static final String DATABASE_NAME = "my-db";
private static final String USERNAME = "user";
private static final String PASSWORD = "strong-password";
public static AppMariaDBContainer container = new AppMariaDBContainer()
.withDatabaseName(DATABASE_NAME)
.withUsername(USERNAME)
.withPassword(PASSWORD);
public AppMariaDBContainer() {
super(IMAGE_VERSION);
}
}
Create an extension class that starts the container and sets the DataSource properties. And run migrations if needed:
public class DatabaseSetupExtension implements BeforeAllCallback {
#Override
public void beforeAll(ExtensionContext context) {
AppMariaDBContainer.container.start();
updateDataSourceProps(AppMariaDBContainer.container);
//migration logic here (if needed)
}
private void updateDataSourceProps(AppMariaDBContainer container) {
System.setProperty("spring.datasource.url", container.getJdbcUrl());
System.setProperty("spring.datasource.username", container.getUsername());
System.setProperty("spring.datasource.password", container.getPassword());
}
}
Add #ExtendWith to your test class
#SpringBootTest
#ExtendWith(MariaDBSetupExtension.class)
class ApplicationIntegrationTests {
#Test
void someTest() {
}
}
Another test
#SpringBootTest
#ExtendWith(MariaDBSetupExtension.class)
class AnotherIntegrationTests {
#Test
void anotherTest() {
}
}
Using either singleton containers or reusable containers are possible solutions but because they don't scope the life-cycle of the container to that of the application context both are less then ideal.
It is however possible to scope the container to the application contexts lifecycle by using a ContextCustomizerFactory and I've written about this in more detail in a blog post.
In a test use:
#Slf4j
#SpringBootTest
#EnabledPostgresTestContainer
class DemoApplicationTest {
#Test
void contextLoads() {
log.info("Hello world");
}
}
Then enable the annotation in META-INF/spring.factories:
org.springframework.test.context.ContextCustomizerFactory=\
com.logarithmicwhale.demo.EnablePostgresTestContainerContextCustomizerFactory
Which can be implemented as:
public class EnablePostgresTestContainerContextCustomizerFactory implements ContextCustomizerFactory {
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Inherited
public #interface EnabledPostgresTestContainer {
}
#Override
public ContextCustomizer createContextCustomizer(Class<?> testClass,
List<ContextConfigurationAttributes> configAttributes) {
if (!(AnnotatedElementUtils.hasAnnotation(testClass, EnabledPostgresTestContainer.class))) {
return null;
}
return new PostgresTestContainerContextCustomizer();
}
#EqualsAndHashCode // See ContextCustomizer java doc
private static class PostgresTestContainerContextCustomizer implements ContextCustomizer {
private static final DockerImageName image = DockerImageName
.parse("postgres")
.withTag("14.1");
#Override
public void customizeContext(ConfigurableApplicationContext context, MergedContextConfiguration mergedConfig) {
var postgresContainer = new PostgreSQLContainer<>(image);
postgresContainer.start();
var properties = Map.<String, Object>of(
"spring.datasource.url", postgresContainer.getJdbcUrl(),
"spring.datasource.username", postgresContainer.getUsername(),
"spring.datasource.password", postgresContainer.getPassword(),
// Prevent any in memory db from replacing the data source
// See #AutoConfigureTestDatabase
"spring.test.database.replace", "NONE"
);
var propertySource = new MapPropertySource("PostgresContainer Test Properties", properties);
context.getEnvironment().getPropertySources().addFirst(propertySource);
}
}
}
I'm not sure how #Testcontainers works, but I suspect it might work per class.
Just make your singleton static as described in Singleton pattern
and get it in every test from your signleton holder, don't define it in every test class.

Java Spring Test Autowired Controller is null error

I would like to test a Java Web Controller using Spring (Not Spring Boot).
My Controller is
#Controller
#RequestMapping("/orders")
public class OrderHdrController {
#RequestMapping(value = "/getOrderList", method = RequestMethod.POST)
#ResponseBody
public Map<String, Object> getOrderTables(OrderSearchDto orderSearchDto) { ... }
}
And my test class is:
public class FilterActivityTest2 {
#Autowired
private OrderHdrController orderHdrController;
#Test
public void testActivity() {
OrderSearchDto orderSearchDto = new OrderSearchDto();
OrderSearchPanelDto orderSearchPanelDto = new OrderSearchPanelDto();
orderSearchPanelDto.setActivityTypes(Arrays.asList("TAKEOVER","DELIVERY"));
orderSearchDto.setOrderSearchPanelDto(orderSearchPanelDto);
Map<String, Object> result = orderHdrController.getOrderTables(orderSearchDto);
assertNotNull(result);
}
}
I do not want to mock any objects. I just want to run the test on the controller all the way to the db. But when I debug into the test, the orderHdrController is null in testActivity method.
What have I done wrong? Please help or ask me for more information. Thanks.
#RunWith(SpringRunner.class)
#WebMvcTest(OrderHdrController.class)
public class FilterActivityTest2 {
#Autowired
private OrderHdrController orderHdrController;
#Test
public void testActivity() {
OrderSearchDto orderSearchDto = new OrderSearchDto();
OrderSearchPanelDto orderSearchPanelDto = new OrderSearchPanelDto();
orderSearchPanelDto.setActivityTypes(Arrays.asList("TAKEOVER","DELIVERY"));
orderSearchDto.setOrderSearchPanelDto(orderSearchPanelDto);
Map<String, Object> result = orderHdrController.getOrderTables(orderSearchDto);
assertNotNull(result);
}
}
or If dont using any spring or junit then why using #test
simply make a main class
public class FilterActivityTest2{
public static void main(String args[]){
....... put your tast case code here
}
}
Your FilterActivityTest2 needs to be managed by the Spring context, to be able to autowire dependencies.
To do that, either annotate your test class with:
#RunWith(SpringRunner.class)
#SpringBootTest
Or extend the main test class that already has these annotations. If you created your project using spring initializer, you'll find that class in the tests created for you.
public class FilterActivityTest2 extends MyApplicationTests {
EDIT
For Spring, you can use #ContextConfiguration. Here is a good tutorial.
Also see the official documentation here.
If you do not use spring boot then you can create the application context manually
#Before
public void init() {
ApplicationContext context = desired implementation;
controller = context.getBean("bean name");
}
But better do this
#RunWith(MockitoJUnitRunner.class)
public class FilterActivityTest2 {
private OrderHdrController orderHdrController;
#MockBean
private Service service;
#MockBean
private Dao dao;
#Before
public void init() {
orderHdrController = new OrderHdrController(service, dao ....);
}
#Test
....
}

Running a single test from a Suite with #ClassRule fails

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) ?
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.
Full code example of what follows in this post 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 (or WebApplicationContext) 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

Want to run many jUnit test classes with a shared #BeforeClass/#AfterClass and Spring App Context

I am making some integration tests that load data into a remote test database before the integrations tests. However, it is a lot of data so I'd prefer to do it only once before all of my integrations tests.
I've gotten #BeforeClass/#AfterClass working with using either #RunWith(Suite.class) and JUnitCore.runClasses() to run all my test classes as a Suite. However, I am stuck on how to get Spring to Autowire resources that are needed for the setup and teardown. example:
public class AbstractTest {
#Autowired
private SessionFactory sf;
#BeforeClass
public static void setup() {
sf.getCurrentSession().createQuery("make tables");
}
}
But sf is always null, because #BeforeClass needs to be run from a static context.
I have also tried using #ClassRule as shown here: How to share JUnit BeforeClass logic among multiple test classes
but with no change;
How do I get both the #BeforeClass/#AfterClass functionality for a suite of test classes and have Autowired resources in the #BeforeClass/#AfterClass methods? Getting this to run with #Parameterized as well would be even better.
SpringJUnit4ClassRunner calls BeforeClass before autowired . Context is construced ,but not injected yet , by one be injected after calling BeforeClass methods.
from RunBeforeTestClassCallbacks :
public void evaluate() throws Throwable {
for (FrameworkMethod before : befores) { -- call for BeforeClass methods
before.invokeExplosively(target);
}
next.evaluate(); -- call DependencyInjectionTestExecutionListener
--where context is injected
}
You can try this (instead of #BeforeClass use afterPropertiesSet from InitializingBean - it's executed BeforeClass call but before all test stat be executed)
public abstract class AbstractTest implements InitializingBean{
#Autowired
private SessionFactory sf;
//#BeforeClass
#Override
public void afterPropertiesSet() throws Exception {
sf.getCurrentSession().createQuery("make tables");
}
or
public abstract class AbstractTest {
private static SessionFactory sf;
#Autowired
public void setSessionFactory (SessionFactory sf){
AbstractTest.sf = sf;
setup();
}
//#BeforeClass
public static void setup() {
sf.getCurrentSession().createQuery("make tables");
}
}
In any case it be some work around as BeforeClass for tests classes is celled before spring context .
Variant 2 , updated
#TestExecutionListeners(listeners = {AbstractTest.class, DependencyInjectionTestExecutionListener.class})
public class AbstractTest extends AbstractTestExecutionListener {
public static SessionFactory sf;
#BeforeClass
public static void setup() {
sf.getCurrentSession().createQuery("make tables");
}
#Override
public void beforeTestClass(TestContext testContext) throws Exception {
sf = testContext.getApplicationContext().getBean(SessionFactory.class);
super.beforeTestClass(testContext);
}
}

Categories