Triggering Flyway migration before each test - spring

I'm implementing an integration test suite in a Spring Boot project. My project is using Flyway for database migration.
My test class looks similar to this:
#SpringBootTest
#AutoConfigureMockMvc
#Testcontainers
internal class MyIntegrationTest {
#Container
private val postgres = postgresContainer
#Autowired
private lateinit var mockMvc: MockMvc
// ... several test cases
}
Spring Boot runs Flyway migration at application context startup. The problem is that #SpringBootTest starts application context only once, so my DB is initialized only before all tests and not before each test.
I also tried injecting a Flyway field in my test class:
#Autowired
private lateinit var flyway: Flyway
#BeforeEach
fun setup() {
flyway.migrate()
}
but there is No qualifying bean of type 'org.flywaydb.core.Flyway' available.
So, which is the right way to trigger migration before each test?
NOTES:
(PostgreSQL) test container is correctly created and started for each single test.
I'm using this config in application.properties: spring.flyway.enabled=false

Found a solution thinkering with JUnit and Spring Boot configuration.
The reason for No qualifying bean of type 'org.flywaydb.core.Flyway' available was my wrong configuration: spring.flyway.enabled=false. This is disabling also the creation of a Flyway instance, not only the initial migration.
So I removed this property from application.properties, and added this code to my test class (as also suggested by Onome Sotu in his comment):
#Autowired
private lateinit var flyway: Flyway
#BeforeEach
fun setup() {
flyway.clean()
flyway.migrate()
}
I also added #TestInstance(TestInstance.Lifecycle.PER_CLASS) to the test class: this way the container is created and started just once (no need to recreate the database from scratch now that I can clean it before each test). Additionally, this makes tests execution very faster.
NOTE: Actually, with this setup, container creation is executed twice: one at context startup and one on test class creation. Similarly, DB migration is executed for each test case +1 (the +1 happens at context startup). I'd like to find a way to disable unnecessary container creation and database migration)...

Try annotating your tests like this:
#Test
#FlywayTest
public void testFunction(){..}
From Flyway documentation:
Annotation FlywayTest for database unit testing. Use Flyway feature.
clean - execution of flyway task clean
init - execution of flyway task init
migrate - execution of flyway task migrate
https://github.com/flyway/flyway-test-extensions
Also, make sure to include the following dependencies:
testCompile 'org.flywaydb:flyway-core:6.4.1'
testCompile 'org.flywaydb.flyway-test-extensions:flyway-spring-test:6.3.3'

Related

#DataJpaTest loads context so fails on missing beans

Been at this for a few hours now and tried various things with no avail.
I am trying to write basic #DataJpaTest classes to test my native queries etc.
The issue is the test seems to try load the entire SpringApplication context rather than the "slice" required for the tests.
reflectoring.io - Data JPA Tests
As far as I understand, #DataJpaTest should only loads up the bare minimum beans for Entity Management, Repositories and a few other basic beans.
I am overriding the "default H2 database" it uses (as I have custom native SQL queries) so my test looks like:
#DataJpaTest
#ActiveProfiles("jpa")
#Log4j2
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class BrandServiceJpaDataTest {
#Autowired
private WebApiBrandServiceRepo brandServiceRepo;
#Autowired
private BrandRepo brandRepo;
#Test
#DisplayName("Get Brand Services from REPO")
public void getBrandServices() {
final List<BrandService> all = brandServiceRepo.findAll();
log.info(all);
}
}
Properties are simple, I was trying to use #TestContainers but would rather have this working with a basic local hosted DB:
#============ DATABASE ===========#
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/aspwtest
spring.datasource.username=dev
spring.datasource.password=password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect
spring.jpa.show-sql=true
# Hibernate ddl auto (create, create-drop, validate, update)
spring.jpa.hibernate.ddl-auto = create-drop
When running the test, I can see in the logs the application context being loaded up with and leading to missing bean errors as it seems to be trying to load far too much of the context (i.e. missing RestTemplate/Validation beans etc.).
Description:
Field validator in ms.allstar.wallet.webserver.patching.EntityPatcher required a bean of type 'javax.validation.ValidatorFactory' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'javax.validation.ValidatorFactory' in your configuration.
Is there some form of config I am missing?
I know I could probably go around annotating some beans like #ActiveProfile("jpadatatest") but that feels very hacky and would sort of defeat the purpose of #DataJpaTest existing.

Are the project beans already instantiated when we try to run a junit test in spring boot

I am new to Spring and spring boot.
For my spring boot application which is a rest controller, I have some beans along with my data source.
I use my data source to create jdbc template. Now when I am in my rest controller code, I have all these beans #Autowired and they work perfectly fine.
My query is regarding the junit testing part.
When I write my test code inside src/test/java and when I execute my test class within IDE, are the beans defined in my src/main/javacode, instantiated before test case execution?
You might use the same container, or instantiate another container particularly for testing purposes, for which you'll provide a configuration of that other Spring Container separately:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("classpath:test-context.xml")
public class SomeClassTest{...}
However, you can also enable support for loading your Application Context and then use the #Autowired fields in your JUnit fixtures, which also works fine too:
#RunWith(SpringRunner.class)
public class SomeTestClass {
....
#Autowired
ApplicationContext context;
....
}
From here, you can get any bean you wish.

spring integration test fail to load context "Another resource already exists with name dataSource"

I am using test annotation introduced in spring-boot 1.4.3 for my integration tests
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyServiceIT { }
According to documentation, test context is cached and reused to speed up integration tests. This behavior is what I want since it takes significant amount of time to initialize application context. My failsafe plugin is configured with
<forkCount>1</forkCount>
<reuseForks>true</reuseForks>
to allow integration tests to run in the same process to take advantage of application context caching.
Recently, I wrote a integration test used #MockBean annotation to mock behavior for some beans.
#RunWith(SpringRunner.class)
#SpringBootTest
public class AnotherServiceIT {
#MockBean
SomeService service1
}
While the test runs fine on it's own, when running through maven verify, multiple integration tests fails with the error message
javax.naming.NamingException: Another resource already exists with
name dataSource - pick a different name
If I skip this particular test with JUnit #Ignore annotation, everything goes back to normal.
This behavior seems to indicate that using #MockBean changes the caching behavior, and each test attempts to create its own datasource. I should also mention that I am using an AtomikosDataSourceBean created through XADataSourceAutoConfiguration.
How can I overcome this issue so my integration test can still use cached context and use #MockBean at the same time?
Hmm, does SomeService relate to your Datasource in any way?
Because your context is cached and #MockBean does the following:
used to add mocks to a Spring ApplicationContext ... Any existing single bean of the same type defined in the context will be replaced by the mock,
and
If there is more than one bean of the requested type, qualifier metadata must be specified at field level:
#RunWith(SpringRunner.class)
public class ExampleTests {
#MockBean
#Qualifier("example")
private ExampleService service;
Edit:
So if your SomeService is an implementation of a DataSource try adding a Qualifier. If SomeService has a DataSource in it, and you need to access some methods in it, you could try to use #Mock and specify the any objects that need to be returned either through their own mock or autowire.
#Mock
SomeService someService;
#Mock
SomeDependency mockDependency;
#Autowired
OtherDependency realDependency;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
doReturn(mockDependency).when(someService).getSomeDependency();
doReturn(realDependency).when(someService).getOtherDependency();
}

Initializing Mongo DB for Spring Boot Test

I'm building a Spring Boot application that uses Spring Data Repositories with MongoDB. I'm attempting to create a Spock functional Spec to test my repository but I can't figure out the appropriate way to initialize the Mongo DB in preparation for testing. So far I have tried the following:
Do Nothing - This resulted in the same database being used from test to test with my tests failing after.
Drop the database before testing - This resulted in the indexes being lost and me being unable to test my unique indexes.
Here's what I was doing with dropping the database:
#ContextConfiguration(classes = MyApp, loader = SpringApplicationContextLoader)
#ActiveProfiles('test')
class UserRepositoryTest extends Specification {
#Shared
boolean mongoReset = false
#Autowired
MongoTemplate mongoTemplate
#Autowired
UserRepository userRepository
void setup() {
if (!mongoReset) {
mongoTemplate.getDb().dropDatabase()
mongoReset = true
}
}
}
Ideally I'd like to be able to use something similar to the data.sql method provided with JPA repositories.
We usually recommend to rather use the repository to wipe the database (i.e. calling userRepository.deleteAll()). Dropping the database has the downside of wiping all the indexes that might have been created during context bootstrap time.

Spring JUnit Cannot Run TransactionSynchronizationManager.bindResource, while Normal Spring Env Can

I have a working Spring/Hibernate based web application. Now I need to use Spring JUnit 4 to write an integration test for it.
Here is my test code:
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionConfiguration(transactionManager = "hibernateTransactionManager", defaultRollback = true)
#ContextConfiguration(locations = {"classpath:applicationContext-xxx.xml", "classpath:applicationContext-xxx.xml", "classpath:applicationContext-xxx.xml", "classpath:applicationContext-xxx.xml", "classpath:applicationContext-xxx.xml", "classpath:applicationContext-xxx.xml", "classpath:applicationContext.xml"})
public class TestXXX extends AbstractTransactionalJUnit4SpringContextTests {
#Test
public void testXXXExecute(){...}
}
With this setting of the test environment, I can access all the beans and use the sessionFactory bean to get data from database.
The problem happens with one test, with calls a production code using TransactionSynchronizationManager to implement two-phase commit.
The code looks like this:
TransactionSynchronizationManager.bindResource(sessionFactoryA, new SessionHolder(sessionA));
TransactionSynchronizationManager.bindResource(sessionFactoryB, new SessionHolder(sessionB));
The code performs well in the dev and production environment, where the full Spring Framework is running. During the JUnit run, the exception is:
[junit] java.lang.IllegalStateException: Already value [org.springframework.orm.hibernate3.SessionHolder#6311e359] for key [org.hibernate.impl.SessionFactoryImpl#56d47236] bound to thread [main]
I cannot use 2 lines of #TransactionConfiguration in the test class to define the two transaction managers that corresponds to the two data sources and two sessionFactory objects. I wonder if AbstractTransactionalJUnit4SpringContextTests cannot duplicate the transaction environment of the real Spring Framework.
Without seeing more of your code, it is difficult to tell exactly what is wrong. In instances where I've seen this error in the past, it was because files named in the #ContextConfiguration included each other. For example, you might have file
applicationContext-bean-cfg.xml
that includes
applicationContext-hibernate-cfg.xml, but then have
#ContextConfiguration(locations = {"classpath:/applicationContext-bean-cfg.xml", "classpath:/applicationContext-hibernate-cfg.xml"}).
The other thing to check is that one of the files doesn't already have a transaction manager defined.

Resources