Disable #EnableScheduling on Spring Tests - spring

When I run my unit tests, it invokes my scheduled tasks. I want to prevent this behaviour, which is caused by the fact that I have #EnableScheduling on my main app configuration.
How can I disable this on my unit tests?
I have come across this question/answer which suggests setting up profiles?
Not sure how I would go about that? or if its an overkill? I was thinking of having a separate AppConfiguration for my unit tests but it feels like im repeating code twice when I do that?
#Configuration
#EnableJpaRepositories(AppConfiguration.DAO_PACKAGE)
#EnableTransactionManagement
#EnableScheduling
#ComponentScan({AppConfiguration.SERVICE_PACKAGE,
AppConfiguration.DAO_PACKAGE,
AppConfiguration.CLIENT_PACKAGE,
AppConfiguration.SCHEDULE_PACKAGE})
public class AppConfiguration {
static final String MAIN_PACKAGE = "com.etc.app-name";
static final String DAO_PACKAGE = "com.etc.app-name.dao";
private static final String ENTITIES_PACKAGE = "com.etc.app-name.entity";
static final String SERVICE_PACKAGE = "com.etc.app-name.service";
static final String CLIENT_PACKAGE = "com.etc.app-name.client";
static final String SCHEDULE_PACKAGE = "com.etc.app-name.scheduling";
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(){
// stripped code for question readability
}
// more app config code below etc
}
Unit test example.
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={AppConfiguration.class})
#Transactional
#TransactionConfiguration(defaultRollback = true)
#WebAppConfiguration
public class ExampleDaoTest {
#Autowired
ExampleDao exampleDao;
#Test
public void testExampleDao() {
List<Example> items = exampleDao.findAll();
Assert.assertTrue(items.size()>0);
}
}

If you don't want to use profiles, you can add flag that will enable/disable scheduling for the application
In your AppConfiguration add this
#ConditionalOnProperty(
value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true
)
#Configuration
#EnableScheduling
public static class SchedulingConfiguration {
}
and in your test just add this annotation to disable scheduling
#TestPropertySource(properties = "app.scheduling.enable=false")

I just parameterized my #Scheduled annotation with configurable delay times:
#Scheduled(fixedRateString = "${timing.updateData}", initialDelayString = "${timing.initialDelay}")
In my test application.yaml:
timing:
updateData: 60000
initialDelay: 10000000000
And main application.yaml:
timing:
updateData: 60000
initialDelay: 1
It's not turning it off but creating such a long delay, the tests will be long over before it runs. Not the most elegant solution but definitely one of the easiest I've found.

One more solution without any change in production code, using the #MockBean.
#RunWith(SpringRunner.class)
#SpringBootTest
#MockBean(MyScheduledClass.class)
public class MyTest {
Which will eventually replace active scheduled job or create a mocked one.
From the documentation
Mocks can be registered by type or by {#link #name() bean name}. Any existing single
bean of the same type defined in the context will be replaced by the mock, if no
existing bean is defined a new one will be added.

An alternative would be to unregister the bean post processor that schedules the events. This can be done by simply putting the following class on the classpath of your tests:
public class UnregisterScheduledProcessor implements BeanFactoryPostProcessor {
#Override
public void postProcessBeanFactory(final ConfigurableListableBeanFactory beanFactory) throws BeansException {
for (String beanName : beanFactory.getBeanNamesForType(ScheduledAnnotationBeanPostProcessor.class)) {
((DefaultListableBeanFactory)beanFactory).removeBeanDefinition(beanName);
}
}
}
While this is quite simple and seems to do the job, beware that I did not test this very much or check for possible implications of removing a defined bean from the registry or making sure that ordering of PostProcessors won't be an issue...

With Spring Boot and cron expression you can enable or disable scheduling.
For example you can define an test application.yml and set
scheduler:
cron-expr: '-'
See also disable scheduling with '-'.
In your scheduler class you can pass the expression.
#Scheduled(cron = "${scheduler.cron-expr}")

Discovered that adding
app.scheduling.enable=false
in test application.properties along with
#ConditionalOnProperty(value = "app.scheduling.enable", havingValue = "true", matchIfMissing = true)
#EnableScheduling
to scheduling configuration class annotations like in Marko Vranjkovic's answer works for all tests without need to annotate each of them!

In each Test you define which spring configuration should be used, currently you have:
#ContextConfiguration(classes={AppConfiguration.class})
Common practice is to define separate spring configuration for your normal application and for your tests.
AppConfiguration.java
TestConfiguration.java
Then in your test you simply refference TestConfiguration instead of your current AppConfiguration using #ContextConfiguration(classes={TestConfiguration.class})
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes={TestConfiguration.class})
#Transactional
#TransactionConfiguration(defaultRollback = true)
#WebAppConfiguration
public class ExampleDaoTest
This way you can configure any setting for your tests differently than in production code. You can for example use in-memory database for your tests instead of regular one and much more.

I was able to solve this problem by creating a method that removes the scheduled tasks during unit tests.
Here is an example:
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.springframework.context.ApplicationContext;
public static void removeScheduledTasks(ScheduledAnnotationBeanPostProcessor postProcessor, ApplicationContext appContext) {
postProcessor.setApplicationContext(appContext);
postProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);
}
}
Use example:
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.annotation.ScheduledAnnotationBeanPostProcessor;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import com.example.Utils;
#RunWith(SpringRunner.class)
#SpringBootTest
public class TestRemoveScheduller {
#Autowired
private ScheduledAnnotationBeanPostProcessor postProcessor;
#Autowired
private ApplicationContext appContext;
#Before
public void init(){
//Some init variables
//Remove scheduled tasks method
Utils.removeScheduledTasks(postProcessor, appContext);
}
//Some test methods
}
Hope this helps.

I was looking to do this in a normal class (not a unit test). I have my main Spring Boot application but needed a small utility class to do some bulk data cleanup. I wanted to use the full application context of my main app but turn off any scheduled tasks. The best solution for me was similar to Gladson Bruno:
scheduledAnnotationBeanPostProcessor.getScheduledTasks().forEach(ScheduledTask::cancel);
Another advantage of this approach is you can get a list of all scheduled tasks, and you could add logic to cancel some tasks but not others.

create TestTaskScheduler Bean in test class
public class TestTaskScheduler implements TaskScheduler {
private static final NullScheduledFuture NULL_SCHEDULED_FUTURE = new NullScheduledFuture();
#Override
public ScheduledFuture<?> schedule(Runnable task, Trigger trigger) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> schedule(Runnable task, Date startTime) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, Date startTime, long period) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleAtFixedRate(Runnable task, long period) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, Date startTime, long delay) {
return NULL_SCHEDULED_FUTURE;
}
#Override
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable task, long delay) {
return NULL_SCHEDULED_FUTURE;
}
private static class NullScheduledFuture implements ScheduledFuture {
#Override
public long getDelay(TimeUnit unit) {
return 0;
}
#Override
public int compareTo(Delayed o) {
return 0;
}
#Override
public boolean cancel(boolean mayInterruptIfRunning) {
return false;
}
#Override
public boolean isCancelled() {
return false;
}
#Override
public boolean isDone() {
return false;
}
#Override
public Object get() throws InterruptedException, ExecutionException {
return null;
}
#Override
public Object get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
return null;
}
}
}

I would go for TestExecutionListener,
example:
public class SchedulerExecutionListener implements TestExecutionListener {
#Override
public void beforeTestClass(#NonNull TestContext testContext) {
try {
ScheduledAnnotationBeanPostProcessor schedulerProcessor = testContext.getApplicationContext().getBean(ScheduledAnnotationBeanPostProcessor.class);
schedulerProcessor.destroy();
} catch (Exception ignored) {
ignored.printStackTrace();
System.out.println(ignored.getMessage());
}
}
And then you add it to ur testClass
#TestExecutionListeners(listeners = SchedulerExecutionListener .class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
class Test {}

The way that I have solved this is for spring boot applications is by disabling the #EnableScheduling configuration for the test profile:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.scheduling.annotation.EnableScheduling;
#Configuration
#Profile({"!test"})
#EnableScheduling
public class SchedulingConfiguration {
}

My application properties are stored in application.yml so simple add ConditionalOnProperty to scheduler:
#ConditionalOnProperty(value = "scheduling.enabled", havingValue = "true", matchIfMissing = true)
#EnableScheduling
And disable develop environment in application.yml:
environments:
development:
scheduling:
enabled: false

Related

Spring Boot Injecting Implementations for Prod and Test

I'm new to spring boot and I'm trying to wrap my head around how to make dependency injection work for deployment and testing.
I have a #RestController and a supporting #Service. The service injects another class that is an interface for talking to Kafka. For the Kafka interface I have two implementations: one real and one fake. The real one I want to use in production and the fake in test.
My approach is to use two different configuration for each environment (prod and test).
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
Then in my main application I would like to somehow load AppConfiguration.
#SpringBootApplication
public class DeployerServiceApiApplication {
public static void main(String[] args) {
SpringApplication.run(DeployerServiceApiApplication.class, args);
}
// TODO: somehow load here...
}
And in my test load the fake configuration somehow
#SpringBootTest
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest {
#Autowired private MockMvc mockMvc;
// TODO: somehow load AppTestConfiguration here
#Test
public void testDeployAction() throws Exception {
...
ResultActions resultActions = mockMvc.perform(...);
...
}
}
I've spent the better part of a day trying to figure this out. What I'm trying to accomplish here is fundamental and should be straight forward yet I keep running into issues which makes me wonder if the way I'm thinking about this is all wrong.
Am not sure if i understand your question completely but from description i guess you wish to initialize bean based on environment. Please see below.
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
and then you can pass the "-Dspring.profiles.active=prod" argument while starting you application using java command or you can also specify the profile in your test case like below.
#SpringBootTest
#ActiveProfile("test")
#AutoConfigureMockMvc(addFilters = false)
public class DeployerServiceApiApplicationTest
Use spring profiles, you can annotate your test class with #ActiveProfiles("test-kafka") and your test configuration with #Profile("test-kafka").
This is pretty easy task in spring boot world
Rewrite your classes as follows:
#Profile("test")
#Configuration
public class AppTestConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherFakeImpl();
}
}
#Profile("prod")
#Configuration
public class AppConfiguration {
#Bean
public KafkaMessagePublisher kafkaMessagePublisher() {
return new KafkaMessagePublisherImpl();
}
}
This will instruct spring boot to load the relevant configuration when the "prod"/"test" specified.
Then you can start your application in production with --spring.profiles.active=prod and in the Test you can write something like this:
#SpringBootTest
#ActiveProfiles("test")
public class DeployerServiceApiApplicationTest {
...
}
If you want to run all the tests with this profile and do not want to write this ActiveProfiles annotation you can create src/test/resources/application.properties and put into it: spring.active.profiles=test

Spring 2 + JUnit 5, share #MockBean for entire test suite

I create a Spring 2.3 application using Spring Data REST, Hibernate, Mysql.
I created my tests, I've around 450 tests splitted in about 70 files. Because the persistence layer leans on a multi tenant approach (single db per tenant) using a Hikari connection pool, I've the need to avoid the pool is initializated for each test file but at the same time I need to use #MockBean because I need to mock up some repositories in the entire Spring test contest.
I create a custom annotation for all test in my suite:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#SpringBootTest
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public #interface TestConfig {
}
Reading many posts and the doc, I know if I use #MockBean inside a test, the Spring context is reloaded and therefore a new pool connection is created in my case.
My idea is to create a #MockBean and share it with all tests in my suite so the context is not reloaded every time.
I tried several approaches:
#Log4j2
public class TestExecutionListener extends AbstractTestExecutionListener implements Ordered {
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
try {
TestDbUtils testDbUtils = (TestDbUtils) testContext.getApplicationContext().getBean(TestDbUtils.class);
testDbUtils.truncateDB();
TenantRepository tenantRepository = mock(TenantRepository.class);
testContext.setAttribute("tenantRepository", tenantRepository);
TenantContext.setCurrentTenantId("test");
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
} catch (Exception e) {
}
}
#Override
public int getOrder() {
return Integer.MAX_VALUE;
}
}
All my tests are annotated like this:
#TestConfig
#Log4j2
public class InvoiceTests {
#Test
public void test1(){
}
}
Unfortunately my tenantRepository.findByTenantId() is not mocked up. I also tried to create an abstract superclass:
#SpringBootTest
#TestPropertySource(locations = "classpath:application-test.properties")
#TestExecutionListeners(value = TestExecutionListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS)
#Transactional
#ActiveProfiles("test")
public abstract class AbstractIntegrationTest {
#MockBean
protected TenantRepository tenantRepository;
#MockBean
protected SubscriptionRepository subscriptionRepository;
#Autowired
protected TestDbUtils testDbUtils;
#BeforeAll
public void beforeAll() {
when(tenantRepository.findByTenantId("test")).thenReturn(testDbUtils.fakeTenant());
}
#BeforeEach
public void setup() {
testDbUtils.truncateDB();
TenantContext.setCurrentTenantId("test");
}
}
Even if my tests extended this superclass, during the run all of them were skipped (not sure why).
Is there any way to accomplish what I described?

SpringBoot Junit testing main method

I have below test for my spring boot main method.
The test tries to start the application 2 times which is expected.
First time when it starts the application it uses the Mock object hewever 2nd time it starts the application it calls the actual bean.
I have ReferenceDataService having #PostConstract method call which makes rest call to some other application which I don't want in my tests.
Another thing is that MqConfiguration which tries to connect to IBM queues that also I would like to avoid in my test.
Please note even though I have added #ComponentScan(excludeFilters... in my test class it does not exclude it.
How do I write test for my main method in this case?
#ActiveProfiles(profiles = {"test"})
#RunWith(SpringRunner.class)
#SpringBootTest(classes = MainApplication.class, webEnvironment = WebEnvironment.RANDOM_PORT, properties = {
"camel.springboot.java-routes-include-pattern=**/NONE*"})
#EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, SecurityAutoConfiguration.class})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
#ComponentScan(excludeFilters = {#ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, value = {MqConfiguration.class, ReferenceDataCache.class})})
public class MainApplicationTest {
#MockBean
private MqService mqService;
#MockBean
private ReferenceDataService referenceDataService;
#SpyBean
private ReferenceDataCache cacheSpy;
#Test
public void test() {
Mockito.when(referenceDataService.getCurrencies()).thenReturn(new HashMap<>());
Mockito.when(referenceDataService.getFrequencies()).thenReturn(null);
Mockito.when(referenceDataService.getDayCountTypes()).thenReturn(null);
Mockito.when(referenceDataService.getBusinessDayConverntions()).thenReturn(null);
Mockito.when(referenceDataService.getRateDefinations()).thenReturn(null);
Mockito.when(referenceDataService.getBusinessCalendar()).thenReturn(null);
Mockito.when(referenceDataService.getFinancingTypes()).thenReturn(null);
Mockito.when(referenceDataService.getStaffs()).thenReturn(null);
MainApplication.main(new String[]{});
}
}
MainApplication.java (The class to be tested)
#SpringBootApplication
#EnableJms
#EnableCaching
#AutoConfigureBefore(JmsAutoConfiguration.class)
public class MainApplication {
private static final Logger logger = LoggerFactory.getLogger(MainApplication.class);
public static void main(String[] args) {
SpringApplication.run(MainApplication.class, args);
}
}
One could split it into two separate testing parts as we should strive to test a single functionality per test (Single Responsibility Principle). You could model your testing like below:
#Test
public void applicationContextLoadedTest(){
}
#Test
public void applicationStartTest() {
//you can add your mocks as per your required dependencies and requirements
MainApplication.main(new String[] {});
}
Alternatively, if you are allowed to use PowerMockito, then the following link gives you a working example for verifying static invocations.PowerMockito - SpringBoot test

Pointcut expression not matching the Spring Data method despite specfying exact name in expression

In my Spring Boot project, I have AddressRepository that brings all addresses from Database. I have an Aspect class and a pointcut expression that executes after the findAll() method called. When I execute my test case, the Advice is not being triggered and other methods like findAll(Sort sort), findAll(Pageable pageable) work just fine. I am not sure if this is a bug with Spring Boot or my expression. I tried with Spring Boot 2.0.5 and 2.1.0, nothing seemed to solve my problem
AddressLogging.java
#Aspect
#Configuration
public class AddressLogging {
private Logger log=LoggerFactory.getLogger(AddressLogging.class);
//#Pointcut("execution(* com.springtesting.repo.AddressRepository.*(..))")
#Pointcut("execution(* com.springtesting.repo.AddressRepository.findAll())")
public void getAddresses() {}
#After("getAddresses()")
public void afterAdvice() {
log.error("Log Message: Inside afterAdvice() advice");
}
}
AopTest.java
#RunWith(SpringRunner.class)
#SpringBootTest
public class AopTest {
#Autowired
private AddressRepository addressRepository;
#Test
public void getAddresses() {
//addressRepository.findAll(PageRequest.of(0,20, Sort.by("id")));
addressRepository.findAll();
}
#Test
public void findAddressById() {
addressRepository.findById(1L);
}
}
AddressRepository
public interface AddressRepository extends JpaRepository<Address,Long> {}
A Spring AOP aspect should also be a #Component and be picked up by component scan. I have no idea why you added #Configuration to the aspect instead because there is no configuration here.
Maybe your test of a separate configuration class should bear the #Configuration annotation and you should also activate something like #EnableAspectJAutoProxy(proxyTargetClass = true) and #ComponentScan(basePackages = { "de.scrum_master" }).
Here is a snippet from one of my Spring AOP playground projects (I hardly use it, I don't use Spring AOP or even Spring itself, usually I use the more powerful AspectJ:
package de.scrum_master.app;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration
#EnableAspectJAutoProxy(proxyTargetClass = true)
#ComponentScan(basePackages = { "de.scrum_master" })
public class Application2 {
public static void main(String[] args) throws Exception {
ApplicationContext appContext = new AnnotationConfigApplicationContext(Application2.class);
B b = (B) appContext.getBean("b");
System.out.println(b.getData("bbb"));
A a = (A) appContext.getBean("b");
System.out.println(a.getData("aaa"));
}
}

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