Initializing Mongo DB for Spring Boot Test - spring-boot

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.

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.

Integration test values are inserted to database

I am trying to test a JpaRepository with spring boot integration tests. I'm using the #DataJPATest annotation without the embedded db. I'm using the same MySQL db that I use for development to run integration tests.
#DataJpaTest
#AutoConfigureTestDatabase(replace = NONE)
class UserRepositoryTest {
#Autowired
UserRepository userRepository;
#Test
void findByUsername() {
User u = new User();
u.setUsername("ML");
u.setFirstname("Micheal");
u.setLastname("Lane");
u.setActive(true);
userRepository.save(u);
assertThat(userRepository.findByUsername("M21")).isNotNull();
}
}
The value I inserted in the test is inserted into the actual data base. But I thought with #DataJpaTest the transaction is rolled back at the end of the test.
By default, tests annotated with #DataJpaTest are transactional and roll back at the end of each test. They also use an embedded in-memory database (replacing any explicit or usually auto-configured DataSource). The #AutoConfigureTestDatabase annotation can be used to override these settings.
So why is my data getting saved to the database? Is it because I'm not using the embedded database?
Dialect used : spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
**********************************************************8
It started to rollback once I remove findByUsername() part form the test method
User u = new User();
u.setUsername("ML");
u.setFirstname("Micheal");
u.setLastname("Lane");
u.setActive(true);
userRepository.save(u);
//assertThat(userRepository.findByUsername("M21")).isNotNull();
So I guess It doesn't work because there are 2 queries. I will have to use an already saved data for findByUsername() test
I believe it is due to the auto-wire. When you auto-wire it instantiates the interface via dependency injections. If you don't want data to be stored when running the test I suggest you look at mockito. There you can mock the repos so you can test all the methods used when storing data but never actually saves it
Can you try by using this dialect.
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
Hope this should work.

Triggering Flyway migration before each test

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'

Clean database with Flyway after each test class

we are in situation where we cannot simple rollback data after test, so we decide to use Flyway java API like this:
#Autowired
protected Flyway flyway;
#AfterEach
public void restoreDatabase() {
flyway.clean();
flyway.migrate();
}
Is possible execute clean and migrate after each test class instead of test method? I need call this in #AfterAll annotated static method, but this type of methods have to be static so I cannot use autowired component Flyway. Can you advice me any workaround? Thank you.
The following solution may help you.
Besides the #Rollback annotation there is also the possibility to mark a class (or method) as "dirty" with the annotation org.springframework.test.annotation.DirtiesContext. This will provide the test cases a fresh context. From the Java Docs:
Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache.
Use this annotation if a test has modified the context — for example, by modifying the state of a singleton bean, modifying the state of an embedded database, etc. Subsequent tests that request the same context will be supplied a new context.
#DirtiesContext may be used as a class-level and method-level annotation within the same class or class hierarchy. In such scenarios, the ApplicationContext will be marked as dirty before or after any such annotated method as well as before or after the current test class, depending on the configured methodMode and classMode.
Let me show you an example:
#RunWith(SpringRunner.class)
#SpringBootTest
#DirtiesContext(classMode = ClassMode.BEFORE_CLASS)
public class SomeTestClass {
#Autowired
private ExampleController controller;
#Test
public void testSomething() {
//Do some testing here
}
Now in this case, with an embedded DB (like H2), a fresh DB will be started containing no changes from previous transactions.
Please note that this will most probably slow down your test cases because creating a new context can be time consuming.
Edit:
If you watch the log output you'll see that Spring creates a new application context with everything included. So, when using an embedded DB for the test cases, Spring will drop the current DB and creates a new one and runs all specified migrations to it. It's like restarting the server which also creates a new embedded DB.
A new DB doesn't contain any commits from previous actions. That's why it works. It's actually not hacky imo but a proper set up for integration tests as integration tests mess up the DB and need the same clean setup. However, there are most probably other solutions as well because creating new contexts for every test class may slow down the execution time. So, I would recommend to annotate only classes (or methods) which really needs it. On the other hand, unit tests are in most cases not very time critical and with newer Spring versions lazy loading will speed up startup 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