Running a single test from a Suite with #ClassRule fails - java

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

Related

How to reuse TestContainer ? (Junit 4)

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();
}
}

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.

Pass context from TestSuite to JUnit test classes

I have the following requirement:
Create a TestSuite class which initialize some variables which are required by all test classes.
Pass this variable to all classes.
so, I created the following TestSuite class:
#RunWith(Suite.class)
#SuiteClasses({ //
LoginCommandTest.class //
})
public class GameTestSuite {
private static Vertx vertx;
#BeforeClass
public static void setUp() throws IOException
vertx = ....
...
}
}
and the test-class
public class LoginCommandTest {
#Test
public void testLogin() {
vertx.someMethod();
...
}
}
How can I pass vertx initialized in #BeforeClass of GameTestSuite into LoginCommandTest ??
If you use JUnit 5, have a look at this project, that integrates vert-x with Jupiter: https://github.com/vert-x3/vertx-junit5 It implements an extension that cares about creating and providing (a shared) vert-x context to each test container or method.
If it doesn't fit your needs, roll your own specialized extension. Details at https://junit.org/junit5/docs/current/user-guide/#extensions
It's ugly but you can make vertx public or protected and access it from LoginCommandTest like any other static variable GameTestSuite.vertx.

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);
}
}

#BeforeClass annotation is not working in dynamically created Test Suite

I have few JUnit Tests and I want to decide which one to use at runtime. I checked previous answers at SO and I ended up creating Test Suite dynamically.
This class is where my application starts. I have CustomTestSuite class and Main class adds Tests to my custom suite.
public class Main {
public static junit.framework.TestSuite suite()
{
CustomTestSuite suite = new CustomTestSuite();
suite.addTest(new JUnit4TestAdapter(BTest.class));
suite.addTest(new JUnit4TestAdapter(ATest.class));
return suite;
}
}
CustomTestSuite.java
public class CustomTestSuite extends TestSuite {
#BeforeClass
public static void setUp() throws Exception {
System.out.println("Before class test");
}
#After
public void tearDown() throws Exception {
System.out.println("After class test");
}
}
My ATest and BTest are simple Test classes, I will just show ATest as sample:
public class ATest{
#Test
public void testMethod() {
System.out.println("testMethod");
}
}
When I start running my project from Main class, it is expected to run the method with #BeforeClass first, do testing, and then run the method with #AfterClass annotation.
Tests are working fine but it skips setUp method and tearDown method. I tried #Before and #BeforeClass annotations both.
I am confused with suite structure. Any help would be appreciated.
Thanks
#Before and #BeforeClass are supposed to be used in Test class not in TestSuite. If need to have common setUp and tearDown for more than one Test class, then put those both methods in a super class and extend that super by ATest and BTest test classes. And also the Suite can be built and run simply with #RunWith and #SuiteClasses annotations and the CustomTestSuite class is not needed.
So the changes are as below.
The CustomTestSuite becomes TestSuper
public class TestSuper {
#BeforeClass
public static void setUp() throws Exception {
System.out.println("Before class test");
}
#After
public void tearDown() throws Exception {
System.out.println("After class test");
}
}
Now the ATest extends TestSuper
public class ATest extends TestSuper {
#Test
public void testMethod() {
System.out.println("testMethod");
}
}
Similarly BTest also should extend TestSuper.
Simply add #RunWith and #SuiteClasses annotations to Main class as below and run Main.
#RunWith(Suite.class)
#SuiteClasses({ATest.class, BTest.class})
public class Main {
}
Have a go with these changes.

Categories