Junit Spring avoid to load twice application context datasource - java

I have this configuration classes:
#ComponentScan(
basePackages = {
"mypackage.controller",
"mypackage.service",
"mypackage.repository"
}
)
#TestPropertySource(locations="classpath:configuration.properties")
#Import({
H2Configuration.class
})
public class TestConfiguration {
}
#Configuration
public class H2Configuration {
#Bean
public DataSource dataSource() throws SQLException {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
EmbeddedDatabase db = builder
.setType(EmbeddedDatabaseType.H2)
.addScript("h2/create.sql")
.addScript("h2/insert.sql")
.build();
db.getConnection().setAutoCommit(false);
return db;
}
}
And I have this two class tests:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes = { TestConfiguration.class })
public class FirstRepositoryTest {
#Autowired
MyFirstRepositoryImpl repository;
#Before
public void initTest() {
}
#Test(expected = NullPointerException.class)
public void testNullRecords() {
repository.foo(null, null);
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(loader=AnnotationConfigContextLoader.class, classes = { TestConfiguration.class })
public class SecondRepositoryTest {
#Autowired
MySecondRepositoryImpl repository;
#Before
public void initTest() {
}
#Test(expected = NullPointerException.class)
public void testSomethingNullRecords() {
repository.something(null, null);
}
}
If I run junit test once for each class, all goes well.
In clean install phase tests fails because the application context is initialized twice.
For example it try to create the h2 tables twice and do the insert.sql script twice.
What I have to do for initialize the h2 database and so application context only once?
Thanks

I think you could start looking at the Spring documentation about Integration Testing.
It can also be a good practice to use transactional tests for integration tests (#Transactional), which rollback at the end of each test : see Transaction Management.
To avoid the cost of recreating the ApplicationContext for each test class, the cache may be used as explained here : Context Caching.
For integration testing with Embedded Database, you can also find documentation : Testing Data Access Logic with an Embedded Database.
A note from the previous link, matching your use case :
However, if you wish to create an embedded database that is shared
within a test suite, consider using the Spring TestContext Framework
and configuring the embedded database as a bean in the Spring
ApplicationContext as described in Creating an Embedded Database by
Using Spring XML and Creating an Embedded Database Programmatically.
I hope you will find some useful references.

Another good tip I found from Spring Boot documentation from Embedded Database Support :
They say :
If you are using this feature in your tests, you may notice that the
same database is reused by your whole test suite regardless of the
number of application contexts that you use. If you want to make sure
that each context has a separate embedded database, you should set
spring.datasource.generate-unique-name to true.
So to make each EmbeddedDatabase unique, you may try to create them with :
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
...
.build();

In unit testing you must garantee that every test is repeatible hance context independent. Due to this is not good idea to load the context only once. Is better to reset after the execution. For this you can use #DirtiesContext(classMode = ClassMode.AFTER_CLASS) in your test classes
So you will force your context to restart when the next junit class is launched

So the reason that this is failing is that the database (H2) is resident in memory when you run the tests as part of clean/install. The create/insert scripts have already executed after the first test is run. Any subsequent test execution after this point will result in a re-execution of the same script(s) and the error will occur.
Update your create script with a DROP TABLE IF EXISTS <table name>;. This will ensure that the table is dropped then recreated.
NOTE: I'm not sure why you've specified AnnotationConfigContextLoader explicitly. I think, without that, the runner SpringJUnit4ClassRunner will cache contexts that have not been changed. I don't know specifically if that is the case here though.

Related

#BeforeAll JUnit/spring-boot-test alternative that runs when application context starts

I'm writing a #Repository/#Service integration test that leverages an embedded database. In my test class, I would like to preload my database with some data.
I'm currently using #BeforeEach to load in my sample data, however, this code is run upon each test in my class.
Is there any way that I can load in my test data after Spring application context has loaded, but before any test has been run?
My current approach:
#BeforeEach
public void before() {
repository.save(...); // -> prepopulates repository with sample data
}
#Test
public void testService() {
service.get(...); // -> gathers existing record
}
#Test
public void deleteById() {
service.delete(...); // -> deletes existing record
}
However... with this, I am required to flush out the records after every test. Otherwise any unique constraints can easily be violated.
Rather than using #BeforeEach which is required to run before every test... is it possible to load this in in a #BeforeAll kind of fashion that happens after the spring application context has been loaded?
Is there any way that I can load in my test data after Spring application context has loaded
Basically yes, I think you can do that:
The idea is to load the SQL data when the application context is started or in the process of being started.
For example, spring boot integration with Flyway works this way (the bean of Flyway is created and loaded). So, in theory, you could merely use Flyway with test migrations that will contain all the relevant SQL scripts of test data generation.
How can you do this technically?
Here is one way:
Create a special bean (just like the way it works with Flyway) that would depend on your repository and in post construct save the data:
#Component
public class SqlGenerationBean {
#Autowired
private MyRepository repo;
#PostConstruct
public void init() {
repo.save();
}
}
Another way of doing is to create a listener that will be called upon the application context started and again will call the same repo.save().
In both cases the bean/listener code should not be accessible from production (it's only for tests): so put it somewhere under src/test/java for example
Now once the application context is started you can use a neat trick:
Mark your tests with #Transactional annotation. Spring will wrap the code in an artificial transaction that will be rolled back automatically (even if the test succeeds) so that all the data that you'll modify during the test will be rolled back and basically before each test, you'll have the same state (that is identical to the state of the database when/after the application context starts). Of course, if you use DDL in the test, some databases can't make it a part of transaction but it depends on the database really.
Another interesting point here is that the application context can be cached even between the test cases (created only once), so keep this in mind.
In this case I would just create a constructor for the test class. It will be triggered before everything.
#BeforeEach runs before each tests but after all initialisations .
you can also just use Mockito and mock the result without need to clean and overcomplicate
Just add following snippet to your code. This is just like you can do to detect that Spring application is really started.
#Configuration
public class AppConfig implements ApplicationListener<ApplicationReadyEvent> {
/**
* This is to indicate in the logs when the application has actually started and everything is loaded.
*/
#Override
public void onApplicationEvent(ApplicationReadyEvent event) {
ApplicationContext context = event.getApplicationContext();
Environment env = context.getEnvironment();
// do what you want on application start
}
}
P.S. For database manipulation in test #Sql is the best candidate as was mentioned in comment.

Spring boot with testcontainers - how to prevent DB initialization on context reload

Context
I have a suite of Integration tests in a Spring boot application. The test context uses a MSSQL docker container for it's database using the testcontainers framework.
Some of my tests use Mockito with SpyBean which, apparently by design, will restart the Spring context since the spied beans cannot be shared between tests.
Since I am using a non-embedded database that lives for the duration of all my tests, the database is provisioned by executing my schema.sql and data.sql at the start by using:-
spring.datasource.initialization-mode=always
The problem is that when the Spring context is restarted, my database is re-initialized again which triggers errors such as unique constraint issues, table already exists etc.
My parent test class is as follows if it's of any help:-
#ActiveProfiles(Profiles.PROFILE_TEST)
#Testcontainers
#SpringJUnitWebConfig
#AutoConfigureMockMvc
#SpringBootTest(classes = Application.class)
#ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {
private static final MSSQLServerContainer<?> mssqlContainer;
static {
mssqlContainer = new MSSQLServerContainer<>()
.withInitScript("setup.sql"); //Creates users/permissions etc
mssqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of("spring.datasource.url=" + mssqlContainer.getJdbcUrl())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
}
Each integration test extends this so that the context (for non-spied tests) is shared and setup occurs just once.
What I want
I would like to be able to execute the startup scripts just one time on startup and never again despite any number of context reloads. If the Spring test framework could remember that I already have a provisioned DB, that would be ideal.
I am wondering if there are any existing configurations or hooks that may help me
If something like the following existed, it'd be perfect.
spring.datasource.initialization-mode=always-once
But, as far as I can tell, it doesn't :(
Possible, but incomplete, solutions
Test container init script
new MSSQLServerContainer<>().withInitScript("setup.sql");
This works and ensures I can run a startup script the first time only since the container is started up just once. However withInitScript only takes a single argument rather than an array. As such, I would need to concatenate all my scripts into one file which means I'd have to maintain two sets of scripts.
If you only had one script, this would work fine.
Continue on error
spring.datasource.continue-on-error=true
This works in the sense that startup errors in the schema are ignored. But.. I want it to fail on startup if someone put some dodgy SQL in the scripts.
Spring event hooks
I couldn't get this to work. My idea was that I could listen for the ContextRefreshedEvent and then inject a new value for spring.datasource.initialization-mode=never.
It's a bit of a hack but I tried something like the following
#Component
public static class EventListener implements ApplicationListener<ApplicationEvent> {
#Autowired
private ConfigurableEnvironment environment;
#Override
public void onApplicationEvent(final ApplicationEvent event) {
log.info(event.getClass().getSimpleName());
if (event instanceof ContextRefreshedEvent) {
TestPropertyValues.of("spring.datasource.initialization-mode=never")
.applyTo(this.environment);
}
}
}
My guess is when the context restarts, it will also reload all my original property sources again which has mode=always. I would need an event right after the properties are loaded and right before the schema creation occurs.
So with that, does anyone have any suggestions?
So I ended up finding a workaround for this. Feels hacky but unless someone else is able to suggest a more appropriate and less obscure fix, then this is what I'll go with.
The solution uses a combination of #tsarenkotxt suggestion of AtomicBoolean and my #3 partial solution.
#ActiveProfiles(Profiles.PROFILE_TEST)
#Testcontainers
#SpringJUnitWebConfig
#AutoConfigureMockMvc
#SpringBootTest(classes = Application.class)
#ContextConfiguration(initializers = {IntegrationTest.Initializer.class})
public abstract class IntegrationTest {
private static final MSSQLServerContainer mssqlContainer;
//added this
private static final AtomicBoolean initDB = new AtomicBoolean(true);
static {
mssqlContainer = new MSSQLServerContainer()
.withInitScript("setup.sql"); //Creates users/permissions etc
mssqlContainer.start();
}
static class Initializer implements ApplicationContextInitializer {
#Override
public void initialize(final ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues.of(
"spring.datasource.url=" + mssqlContainer.getJdbcUrl(),
//added this
"spring.datasource.initialization-mode=" + (initDB.get() ? "always" : "never"))
.applyTo(configurableApplicationContext.getEnvironment());
//added this
initDB.set(false);
}
}
}
Basically I set spring.datasource.initialization-mode to be always on the very first startup, since the db hasn't been setup yet, and then reset it to never for every context initialization thereafter. As such, Spring won't attempt to execute the startup scripts after the first run.
Works great but I don't like having to hide this configuration here so still hoping someone else will come up with something better and more "by design"

How to overwrite ports defined in application.properties in integration tests after testcontainer started?

Assume I want to integration test code relying on a JPA datasource in a Spring Boot 2.x application with a PostgreSQL testcontainer (great tool for managing Docker containers from within test classes with one or few more lines of code). Assume further that I'm managing the ports (included in the JDBC URL) in application.properties, e.g.
spring.datasource.url=jdbc:postgresql://user-service-postgres:5432/user_service
In the integration test I create testcontainers with
#Rule
PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer();
In a preparation method I can access the value I want to set for spring.datasource.url with
postgreSQLContainer.getJdbcUrl()
How to tell Spring Boot in the test to use that URL instead of the one specified in application.properties.
I'd like to stick to my property files in order to minimize changes, but I'm thankful for other approaches including an explanation why they're superior or necessary as well.
I'm using Spring Boot 2.x.
Since Spring Framework 5.2.5 (Spring Boot 2.2.6) this setup is now even simpler as we can use the #DynamicPropertySource annotation and don't have to write and register a custom initializer.
Assuming you use the JUnit 5 dependency of Testcontainers, your test can look like the following:
#SpringBootTest
#Testcontainers
class ExampleIntegrationTests {
#Container
static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer();
#DynamicPropertySource
static void dataSourceProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
}
}
You can read more about this new feature here. I've also covered the different application properties setup ways (depending on Spring Boot and JUnit version) in a dedicated Testcontainers guide.
You can manually override the property from within your Spring-boot test by using ContextConfiguration and ApplicationContextInitializer.
Override the property - define a static inner class:
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
TestPropertyValues
.of("spring.datasource.url=" + postgreSQLContainer.getJdbcUrl())
.applyTo(configurableApplicationContext.getEnvironment());
}
}
ApplicationContextInitializer can be used for programmatically initializing a Spring context before context refresh. Now, wire up the context initializer class by annotating at test class level with ContextConfiguration:
#ContextConfiguration(initializers = Initializer.class)
Docs:
ApplicationContextInitializer
ContextConfiguration

Prevent Spring bean re-initialization for each JUnit test

I have integration tests based on JUnit that access DB. We also use Liquibase Spring bean to init database.
If I try running multiple tests in parallel each of them tries to initialize DB using Liquibase causing locks and eventually failures since only one instance of Liquibase can modify DB at a time.
The tests are configured as follows:
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#WebAppConfiguration
#Sql({"/schema/insert-test-data.sql"})
How can I configure DB initialization (schema and data) that it will be done only once and not for each test?
This is just idea not testet, but if you are not executing scripts in parallel what about using ScriptUtils or ResourceDatabasePopulator iside #Before method with some switch.
#Before
public void init(){
if (wasInitialized)
return;
new ResourceDatabasePopulator(new ClassPathResource("path/to/sql.sql")).execute(dataSource);
wasInitialized = true;
}

How to call datasource in Junit

I have a maven WAR package with JSF pages. Usually I use
#Resource(name = "jdbc/Oracle")
private DataSource ds;
to call database when I deploy the WAR package on Glassfish server. But in case of JUnit tests when I build the package on my laptop with netbeans I cannot use this datasource.
How I can solve this problem? I want to run the JUnit tests with database tables right after I build the package but I don't have a datasource.
What are the possible solutions?
Do you actually want to run your unit tests against the database? Personally I would try to avoid this, since it usually ties the test too closely to the state of the database and often prevents you from actually testing the "unit" and all the possible states you might like to handle. It will also probably make your unit tests take some time to run, which is not ideal.
An alternative would be to create a mock DataSource, using for example EasyMock or Mockito. Or you could create your own mock implementation of the DataSource interface, if you know you would like to define some common behaviour for DataSources across many tests.
If you really want to use the database, you would have to look at manually instantiating whatever implementation of DataSource you are using (e.g. OracleDataSource) and then using this in your class.
In either case, you will probably have to switch to using constructor or method injection to make it a bit easier to set the DataSource on the instance you are testing. (Otherwise you will have to use reflection to set the private variable.)
For example, your class might look like this:
public class DatabaseOperations {
private DataSource dataSource;
#Resource(name = "jdbc/Oracle")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
}
And then your test might look like this:
public class DatabaseOperationsTest {
public void testSomeOperation() {
DatabaseOperations databaseOperations = new DatabaseOperations();
databaseOperations.setDataSource(new MockDataSource());
}
}
If you really do need to run tests using an injected DataSource, you could consider using Arquillian which will create a deployment unit for you, deploy it to either an embedeed or remote Glassfish container, together with a configured DataSource specifically for testing if you wish. They have a guide for this specific scenario.
The advantage is that you will have a full-blown container with CDI. You control what gets package so you can provide test stubs for CDI classes. You can also control the deployment configuration (test vs. production configuration). It is non-invasive.

Categories

Resources