How to execute code in a SpringBootTest before the Application is run? - spring-boot

I have a SpringBoot based command line application. The application creates or deletes some records in a database. It does so not directly via JDBC but rather through a special API (instance variable dbService).
The application class looks like this:
#SpringBootApplication
public class Application implements CommandLineRunner {
#Autowired
private DbService dbService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) {
// Do something via dbService based on the spring properties
}
}
Now I'd like to create a SpringBoot test that would run the whole application with a configuration specially prepared for the test.
I run the test with an in-memory DB (H2) which is empty at the test start. Hence I'd like to insert some records into the DB -- as the setup for the test. The code for inserting the records must be executed
After the Spring context has been loaded -- so that I can use the bean dbService.
Before the Application is run -- so that the application runs with the prepared DB.
Somehow I fail to implement the above two points.
What I have so far is this:
#SpringBootTest
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
#ActiveProfiles("specialtest")
public class MyAppTest {
#Autowired
private DbService dbService;
private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
// The expectation is that this method is executed after the spring context
// has been loaded and all beans created, but before the Application class
// is executed.
#EventListener(ApplicationStartedEvent.class)
public void preparedDbForTheTest() {
// Create some records via dbService
logger.info("Created records for the test");
}
// This test is executed after the application has run. Here we check
// whether the DB contains the expected records.
#Test
public void testApplication() {
// Check the DB contents
}
}
My problem is that the the method preparedDbForTheTest does not seem to get executed at all.
According to the SpringBoot docs, the event ApplicationReadyEvent is sent exactly when I want to execute the setup code. But somehow the code is not executed.
If I annotate the method with #Before... (I tried several variants of it) then it gets executed, but after the Application class has run.
What am I doing wrong?

Test classes aren't Spring-managed beans so things like #EventListener methods will be ignored.
The most conventional solution to your problem would be to add some #TestConfiguration that declares the #EventListener:
#SpringBootTest
#DirtiesContext(classMode = ClassMode.AFTER_CLASS)
public class MyAppTest {
private static final Logger logger = LoggerFactory.getLogger(MyAppTest.class);
#Test
public void testApplication() {
}
#TestConfiguration
static class DatabasePreparation {
#EventListener(ApplicationStartedEvent.class)
public void preparedDbForTheTest() {
logger.info("Created records for the test");
}
}
}
A #TestConfiguration is additive so it'll be used alongside your application's main configuration. The preparedDbForTheTest method will now be called as part of refreshing the application context for the tests.
Note that, due to application context caching, this method won't be called for every test. It will only be called as part of refreshing the context which may then be shared among several tests.

Related

Testing ApplicationListener with #SQL

We have a spring bean doing some initialization stuff at bootup of a spring-boot application. To do so, the application registers a ApplicationListener<ApplicationReadyEvent>, which queries the database and acts upon the data.
To test this process, we have to inject testdata in the database. Our first try was to init the database with a test-specific ApplicationListener<ApplicationStartedEvent>. This works, however, the code looks rather nasty. The idea was to use the #Sql annotation instead to load the initial data.
This works not as expected, as the data is injected after the ApplicationReadyEvent has been published. I was unable to find means to change the phase during which the #Sql data is written to the database.
Is there a way to ensure, the data of #Sql is written prior to publishing the ApplicationReadyEvent? The test is currently otherwise annotated to run with SpringRunner, #DataJpaTest and with #DirtiesContext.
Edit: Provide Code
The ApplicationListener ist provided as this:
#Component
public class ApplicationStartup implements ApplicationListener<ApplicationReadyEvent> {
#Override
public void onApplicationEvent(final ApplicationReadyEvent event) {
// Do someting with SQL-Data
}
}
While the test looks like this:
#RunWith(SpringRunner.class)
#ComponentScan
#Import(SomeTestConfig.class)
#DataJpaTest
//#SQL("/somedata.sql")
public class SomeTest {
#Test
public void test() {
// Assert ApplicationListener has run
}
}
With the test-config as follows:
#TestConfiguration
#Profile(ReplayTest.PROFILE_SOME_TEST)
class SomeTestConfig {
#Bean
public ApplicationListener<ApplicationStartedEvent> testSetupBean() {
return new ApplicationListener<ApplicationStartedEvent>() {
// Insert data within onApplicationEvent-Method
};
}
}
If I uncomment #SQL and comment-out the #Import, the testdata is visible from within the test itself but not from within the ApplicationListener.
You can make use of the #Order annotation. You can annotate your application listeners and your test with it. By default, this has lowest precedence. So you should provide higher precedence to your application listener and lowest to your test.
Listener:
#Override
#Order(Ordered.HIGHEST_PRECEDENCE)
public void onApplicationEvent(ApplicationReadyEvent event) {
System.out.println("...publishing application event");
}
Test:
#Test
#Sql(scripts = "test.sql")
#Order
public void test() {
System.out.println("...loading data");
}

How can I run a specific class / utility in a Spring Boot application with wiring?

I have my standard Spring Boot application working. I have situations where I want to run a "job" which is basically some specific method normally run via a user doing something in their browser but I want to run it from command line.
I'm able to run an arbitrary class with gradlew;
./gradlew -PmainClass=kcentral.backingservices.URLMetaExtractor execute
However when run this way none of the "autowiring" works. What is a better way to execute an arbitrary class (that has a main method) such that it also works with any Autowiring?
EDIT:
I got some advice to use a CommandLineRunner and some args, which work to execute the command via:
./gradlew bootRun -Pargs=--reloadTestData
However, the Autowiring of my Repo is failing. What I have is:
#EnableAutoConfiguration
#EnableMongoAuditing
#EnableMongoRepositories(basePackageClasses=KCItemRepo.class)
#ComponentScan(basePackages = {"kcentral"})
public class ReloadTestData implements CommandLineRunner {
#Autowired
AddItemService addItemService;
#Autowired
KCItemRepo itemRepo;
#Autowired
KCItemRatingRepo itemRatingRepo;
private static final Logger log = LoggerFactory.getLogger(ReloadTestData.class);
public void reloadData(){
log.info("reloadData and called");
if (itemRepo == null){
log.error("Repo not found");
return;
}
long c = itemRepo.count();
log.warn("REMOVING ALL items "+c);
itemRepo.deleteAll();
log.warn("REMOVING ALL ratings");
itemRatingRepo.deleteAll();
}
itemRepo is always null even though I wire the same way in my 'regular' spring boot app without an issue. What do I need to do to have it wire properly?
The fact that you say you want to run a "job" suggests that you might want to use a scheduled task within your application, rather than trying to run it through the command line. e.g. Scheduling tasks in Spring
#Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("The time is now {}", dateFormat.format(new Date()));
}
If you want to make a command line application work with Autowiring, you can make a command line application by making your Application class implement the CommandLineRunner interface, e.g. Spring Boot Console App
#SpringBootApplication
public class SpringBootConsoleApplication
implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(SpringBootConsoleApplication.class, args);
}
#Override
public void run(String... args) {
}
}
And add spring.main.web-application-type=NONE to the properties file.
If you want to stop the application after running you can use SpringApplication.exit(ctx). Don't know about your auto-wiring problem though, maybe try printing out the list of available beans which might give some insight. Example:
#Component
public class DoThenQuit implements CommandLineRunner {
#Autowired
private ApplicationContext ctx;
#Override
public void run(String[] args) {
// do some other stuff before quitting
String[] beanNames = ctx.getBeanDefinitionNames();
Arrays.stream(beanNames).forEach(System.out::println);
// then quit the application
SpringApplication.exit(ctx);
}
}

SpringBootTest: how to know when boot application is done

Spring boot integration test looks like this
#RunWith(SpringRunner.class)
#SpringBootTest(classes = Application)
class IntegrationTest {
static QpidRunner qpidRunner
#BeforeClass
static void init() {
qpidRunner = new QpidRunner()
qpidRunner.start()
}
#AfterClass
static void tearDown() {
qpidRunner.stop()
}
}
So, Qpid instance is run before and teared down after all tests. I want to know is there a way to check whether spring boot application is still running before calling qpidRunner.stop(). I want to stop Qpid only when I'm sure that spring app has finished its stopping.
The Spring Boot integration test can configure an ApplicationListener which listens for ContextClosedEvent. Define a nested #TestConfiguration class inside the test class to add beans to the application's primary configuration.
#TestConfiguration
static class MyConfiguration {
#Bean
public ApplicationListener<ContextClosedEvent> contextClosedEventListener() {
return event -> qpidRunner.stop();
}
}
Taking into account that ConfigurableWebApplicationContext can be injected in a SpringBootTest, adding this lines to the code solved the problem
static ConfigurableWebApplicationContext context
#Autowired
void setContext(ConfigurableWebApplicationContext context) {
AbstractDocsIntegrationTest.context = context
}
#AfterClass
static void tearDown() {
context.stop()
qpidRunner.stop()
}
Spring docs about stop method
Stop this component, typically in a synchronous fashion, such that the
component is fully stopped upon return of this method.
JUnit AfterClass annotated method must be static, therefore #Autowired workaround with setContext method.

Spring static context accessor and integration tests

We have a spring component which sets the application context into a static field. This static field is then accessed from other parts of the application. I know static should not be used, but sometimes it is necessary to access spring context from non-spring-managed beans. E.g. the field looks like this:
public class ApplicationContextProvider implements ApplicationContextAware {
private static ApplicationContext context;
public ApplicationContext getApplicationContext() {
return context;
}
#Override
public void setApplicationContext(ApplicationContext ctx) {
context = ctx;
}
}
(taken for http://www.dcalabresi.com/blog/java/spring-context-static-class/)
The problem is that when using JUnit (or Spock) framework in integration tests, a new spring context is created for tests that have annotations like #TestPropertySource or #ContextConfiguration, in that case the contexts are cached for other tests with the same configuration (context caching in spring test framework).
However, the static field is only updated when the spring context is created. That means, when a test context is retrieved from the cache, it does not update the static field, of course, because the context was already initialized before being cached. The static field was already overwritten by the last context created from previous test runs with different configuration and so it does not see the same context as the one that starts the test.
The consequence is that part of the test runs in one spring context and from the point it accesses the static field it runs in the other context.
Does anybody have a solution to this problem? Anybody got into the same situation?
I've faced with tha same issue.
The possible solution might be saving context before test and restoring it after.
For convinience it can be done via junit rule:
public class ContextRestoreRule extends ExternalResource {
private ApplicationContext context;
#Override
protected void before() throws Throwable {
context = ApplicationContextProvider.getContext();
}
#Override
protected void after() {
ApplicationContextProvider.setContext(context);
}
}
And in the test (wich modifies context):
#ClassRule
public static ContextRestoreRule contextRestore = new ContextRestoreRule();

How do I reset my database state after each unit test without making the whole test a transaction?

I'm using Spring 3.1.1.RELEASE, Hibernate 4.1.0.Final, JPA 2, JUnit 4.8.1, and HSQL 2.2.7. I want to run some JUnit tests on my service methods, and after each test, I would like any data written to the in-memory database to be rolled back. However, I do NOT want the entire test to be treated as a transaction. For example in this test
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({ "classpath:test-context.xml" })
public class ContractServiceTest
{
…
#Autowired
private ContractService m_contractService;
#Test
public void testUpdateContract()
{
// Add the contract
m_contractService.save(m_contract);
Assert.assertNotNull(m_contract.getId());
// Update the activation date by 6 months.
final Calendar activationDate = Calendar.getInstance();
activationDate.setTime(activationDate.getTime());
activationDate.add(Calendar.MONTH, 6);
m_contract.setActivationDate(activationDate.getTime());
m_contractService.save(m_contract);
final List<Contract> foundContracts = m_contractService.findContractByOppId(m_contract.getOpportunityId());
Assert.assertEquals(foundContracts.get(0), m_contract);
} // testUpdateContract
there are three calls to the service, ("m_contractService.save", "m_contractService.save", and "m_contractService.findContractByOppId") and each is treated as a transaction, which I want. But I don't know how to reset my in-memory database to its original state after each unit test.
Let me know if I need to provide additional information.
Since you are using Hibernate, you could use the property hibernate.hbm2ddl.auto to create the database on startup every time. You would also need to force the spring context to be reloaded after each test. You can do this with the #DirtiesContext annotation.
This might add a bit extra overhead to your tests, so the other solution is to just manually delete the data from each table.
#DirtiesContext was no solution for me because the whole application context gets destroyed an must be created after each test -> Took very long.
#Before was also not a good solution for me as I have to create #Before in each integration test.
So I decided to create an TestExecutionListener which recreates the database after each test. (With Liquibase, but it also works with Flyway and normal SQL)
public class CleanupDatabaseTestExecutionListener
extends AbstractTestExecutionListener {
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
#Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase(testContext);
alreadyCleared = true;
} else {
alreadyCleared = true;
}
}
#Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase(testContext);
}
private void cleanupDatabase(TestContext testContext) throws LiquibaseException {
ApplicationContext app = testContext.getApplicationContext();
SpringLiquibase springLiquibase = app.getBean(SpringLiquibase.class);
springLiquibase.setDropFirst(true);
springLiquibase.afterPropertiesSet(); //The database get recreated here
}
}
To use the TestExecutionListenere I created a custom test annotation
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#RunWith(SpringRunner.class)
#SpringBootTest(classes = OurderApp.class)
#TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {CleanupDatabaseTestExecutionListener.class}
)
public #interface OurderTest {
}
Last but not least, I can now create tests and I can be sure that the database is in a clean mode.
#RunWith(SpringRunner.class)
#OurderTest
public class ProductSaveServiceIntTest {
}
EDIT: I improved my solution a bit. I had the problem that sometime one test method destroyed my database for all upcoming tests within the test class. So I created the annotation
package com.ourder.e2e.utils;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface ClearContext {
}
and added this to the CleanupDatabaseTestExectionListener.
#Override
public void afterTestMethod(TestContext testContext) throws Exception {
if(testContext.getTestMethod().getAnnotation(ClearContext.class)!=null){
cleanupDatabase(testContext);
}
super.afterTestMethod(testContext);
}
with help of these two snippets I am now able to create tests like this:
#Test
#ClearContext
public void testWhichDirtiesDatabase() {}
If you use flyway for migrations, I use the following pattern:
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class JUnit5Class {
#Autowired
Flyway flyway;
#BeforeAll
public void cleanUp(){
flyway.clean();
flyway.migrate();
}
}
#TestInstance allows you to make #BeforeAll non static and thus you can migrate only once per test class. If you want to reset it for each test remove the class anotation and make change #BeforeAll to #BeforeEach.
As of JUnit 5 you could also create a custom extension and access the data source from the Spring context, like so (using Kotlin):
class DatabaseCleanerExtension : AfterEachCallback {
override fun afterEach(context: ExtensionContext) {
val ds = SpringExtension.getApplicationContext(context).getBean(DataSource::class.java)
ds.connection.use { connection ->
connection.prepareStatement("DELETE FROM my_table").execute()
}
}
}
You can then register the extension as follows:
#SpringBootTest
#ExtendWith(DatabaseCleanerExtension::class)
class SpringJunitExtensionApplicationTests { .. }
Now after each test the callback is executed and you can easily annotate any test classes this applies to.
Here is also a video on settings this up.
Make a #Before method in which you delete all data from database. You are using Hibernate so you can use HQL: delete from Contract.
You can use #Transactional annotation at Junit class level from org.springframework.transaction.annotation.Transactional.
For example:
package org.test
import org.springframework.transaction.annotation.Transactional;
#Transactional
public class ArmyTest{
}
I solve the same problem using a random memory database for each test:
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
#TestPropertySource(properties = {
"spring.datasource.url=jdbc:hsqldb:mem:${random.uuid}"
})
I was having same issue and solved following this
http://www.javafixing.com/2021/10/fixed-how-to-cleanup-h2-db-after-each.html?m=1
basically you can clean the DB after every test method with:
#Sql(scripts = "clean_file.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
on clean_file.sql you can add all the SQL statements to reset the db

Resources