Inserting seed data on application startup fails all my test cases - java

In my web application I have to insert some seed data if not exists in DB when application got deployed. So, I have triggered inserting data api as follows
#Component
public class ApplicationContextLoadListener implements ApplicationListener<ContextRefreshedEvent>{
#Autowired
private PersistenceFacilitator persistenceFacilitator;
#Autowired
private SeedDataService seedDataService;
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// generate metadata for entities and schema
persistenceFacilitator.facilitate();
//insert seed data
seedDataService.insertSeedData();
}
}
It's working fine. But the problem comes when I execute test cases with #RunWith(SpringJUnit4ClassRunner.class), that event got triggered and try to insert seed data though DB is not ready. So, all my test cases getting failed.
Is there a way to address this issue with spring or any good application start-up event to handle seed data?
[updated]
As per app engine specification to test inserting data into DB, I have to call
#Before
public void setup(){
LocalServiceTestHelper helper = new LocalServiceTestHelper(new LocalDatastoreServiceTestConfig());
helper.setUp();
}
Which is not ready by the time application context loads. Anyway I don't want to insert seed data in DB while running testcases

Related

How to save jpa entities on application shutdown?

I have a service which handles CRUD operations for entities using H2 database. Previously during testing phase I used to have a csv file which "acted" as a database and upon application shutdown I saved all in-memory changes to entities to this csv file. Now I want to do the same, but for some reason I am not hitting any breakpoints in my onExit() method and therefore not saving data on application shutdown. I am still working out proper logic for selecting which entities to save, but for now I just want to be able to automatically save when I press Stop 'Application' button in Intellij.
#Component
public class MangaDataProvider {
private static MangaService mangaService;
#Autowired
public MangaDataProvider(MangaService mangaService) {
MangaDataProvider.mangaService = mangaService;
}
#PreDestroy
public static void onExit() {
mangaService.saveAll();
}
}
And then I have a onExit() method in Application:
#SpringBootApplication
public class Application extends SpringBootServletInitializer implements AppShellConfigurator {
public static void main(String[] args) {
LaunchUtil.launchBrowserInDevelopmentMode(SpringApplication.run(Application.class, args));
}
#PreDestroy
public void onExit() {
MangaDataProvider.onExit();
}
}
There is no guarantee that the shutdown hooks will be executed. They have a very limited time to execute. That means you code may be partially executed. You can try to add a Print statement, or a debug breakpoint. You will observe that the first statement is executed because it's super fast, but there is no time left to execute the rest of the method.
I good practice would be to call a service or an endpoint which "prepares" (in your case, it will persist your data) your application for shutdown. You call this service/endpoint then, when execution completed, you can safely terminate the application.
Many server application have a "prepare for shutdown" endpoint. Per example Jenkins ;)

Testing Spring Application with JUnit - Keep data after testclass

I am working on a bigger Spring Boot application that has a lot of tests (more like integration tests). My job now is to speed the process of the tests up. I have found, that testData, we need to test the application are set up multiple times in one testrun, if i run multiple test classes. We use something like this to setup the Data in the classes (edit: the Repository and testDataBuilder are #Autowired):
#BeforeEach
public void setup() {
if (Repository.findByShortId("someId") == null) {
testDataBuilder.createTestData();
}
}
Within the testclass, this works fine. But if my testrun gets to the next class, it seems to drop the data (the data are normally stored in a database, I think within the test the data gets stored in an in - memory database, not sure about that.
I tried multiple things to make this work but nothing did work out in the end:
building data in an abstract class that every test extends
working with #commit on some tests
using a testSuite and trying to create data before all tests like this:
#RunWith(Suite.class)
#SuiteClasses({testClass1.class ...})
#SpringBootTest
#ActiveProfiles({ "unit-test" })
public class testSuite {
#ClassRule
public static setupTestData setup = new setupTestData();
}
this didn't work, because spring does not run at the time, the #ClassRule is run.
What is the best way to setup the testData so not every testClass has to set them up ?
Why doesnt your Test #Configuration map the proper database beans by having H2 inmemory database as a dependency?
Spring/Springboot caches context automatically without you having to do anything special.
If your #Repository uses the H2 memory, then it will be cached across all Tests cases.
H2 can also be configured to write to a file (Instead of memory),if you want it instead of memory.
By default, closing the last connection to a database closes the database. For an in-memory database, this means the content is lost.
To keep the database open, add ;DB_CLOSE_DELAY=-1 to the database URL.
To keep the content of an in-memory database as long as the virtual
machine is alive, use jdbc:h2:mem:test;DB_CLOSE_DELAY=-1.
jdbc:h2:~/test;DB_CLOSE_DELAY=-1; <-- Writes to a file, and disable closing as long as the VM is alive
http://www.h2database.com/html/features.html#database_only_if_exists

#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"

Spring boot + Embedded Kafka + h2 database + unit Tests

So I am using this example Embedded Kafka and this too
I have changed this example little bit and updated the kafka listener with some database (Like h2 db).
Now in my unit test when I want to check that data is available in DB or not I am getting NULL. Also I am not sure how to check DB manually as h2 is a memory base DB.
here is the updated part:
in receiver class
#Autowired
DataTableRepository repository;
#KafkaListener(topics = "${kafkatest.topic}")
public void receive(ConsumerRecord<String, DataTable> consumerRecord) {
LOGGER.info("received payload='{}'", consumerRecord.toString());
repository.save(consumerRecord.value());
latch.countDown();
}
And in unit test :
#Autowired
DataTableRepository repository;
#Test
public void testReceive() throws Exception {
DataTable table = new DataTable(1, "Sending with default template");
template.send(topic, table);
receiver.getLatch().await(10000, TimeUnit.MILLISECONDS);
DataTable dt = repository.getOne(table.getId());
assertNotNull(dt);
assertThat(receiver.getLatch().getCount(), equalTo(0L));
}
But dt is always getting null. Also i am not able to check Database also, as it get stopped after test stopped.
Anybody has any idea how to make this workable?
Have you set the property "kafkatest.topic" in the test properties file? That might be the reason that your listener is not listening to the topic specified.

Categories

Resources