Testing Spring Application with JUnit - Keep data after testclass - java

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

Related

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

How can I load a database with TestPropertySource only once and use it across multiple test classes?

I have a test class that uses #TestPropertySource to load a hsqldb.
Essentially, the test file has gotten quite large, and I would like to break it up a bit. The problem is that loading the DB takes... some time. Not too much, but I don't want to create several test files that each load up the DB.
My code looks like this:
#TestPropertySource(properties = {
"hsqldb.name=SettingsTest"
})
#ContextConfiguration(classes = { settings.config.Config.class }, loader = AnnotationConfigContextLoader.class)
#DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_CLASS)
public class SvcTest
.
.
.
This will run some functions to load up the database. It is my understanding that once the test file is done with all its tests, it will stop the DB. How can I keep it running so other files can use the DB, and close it only when they are done?
Attempting to cache the application context between test classes while at the same time marking the tests as "dirty" using #DirtiesContext is a bit contradictory:
DirtiesContext: Test annotation which indicates that the ApplicationContext associated with a test is dirty and should therefore be closed and removed from the context cache.
If you use the annotation solely to reset the state of your database, you could instead create a ClassRule to manually reset the test data in the database instead of tearing down and rebuilding the full application context.
Furthermore, if the tests are focused on the repository section of the application, Spring offers test slicing. Test slices load a small fragment of the application context, thereby reducing the load time. An example is the #JdbcTest annotation, used for JDBC tests that focus on JDBC-based components.

Skipping integration tests in spring boot on VPN DNS availability

I have a moderately heavy springboot service, it takes 10-15 seconds to boot on a happy flow, and (1-2) minutes to fail on a retry/failover flow. This is ok for my business flows, and is how I expect a healthy service to behave.
I have integration tests (that run some end-to-end flows in my service), that can only test the actual integration status while the test machine (or dev machine) is connected to a specific VPN.
I want to auto skip integration tests if I'm not connected to VPN.
consider the following code
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {Server.class}, // auto scans a bunch of components
webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) // slow loading context
public class IntegrationTest {
#BeforeClass
public static void beforeClass() {
Assume.assumeTrue(DnsTool.vpnConnected()); // fast failing test
}
#Test
public void testIntegration() {
// some test logic
}
}
When the assumptions pass, my tests run, and all is good.
When the assumptions fail, my tests get skipped, but only after trying to load my expensive context.
How can I avoid the long running time for my test suite?
Things I tried:
Subclassing SpringJUnit4ClassRunner, and overriding isTestMethodIgnored.
Adding a TestExecutionListener, and throwing the assumption exception in beforeTestClass
These made no impression on Spring, and the context got loaded any way.
Things I didn't try:
Lazy init comes with 2.2.X next stable release of spring I think.
Lazy init potentially makes my problem go away, but I feel like there should be some easy spring-test/junit fix that I'm missing.
Thanks in advance for the help.
To me, this sounds like something that you shouldn't do in tests at all.
Tests (at least IMHO), are supposed to check the business cases and assume that the environment is set up and ready.
Maybe it worth to delegate this functionality to build tool and CI.
Example:
Define a profile in maven (or whatever build tool you use) that will run integration tests that require VPN. Define profile that will run all the rest of integration tests as well.
Activate the profile if some system property is available.
In CI tool (like Jenkins) as a part of CI even before you run maven, run the script that will check the VPN connection. Based on the results set the system properties and run maven with these properties. The required profiles will be loaded and all the tests / only tests that do not require VPN will be run.
Update
If you need to make it work from Spring (and it looks like you prefer this way),
Spring has a special annotation called #IfProfileValue
By default, it matches against system properties and if the value doesn't match the test gets ignored.
It looks something like this (and note that you can put this annotation on class as well, then it will work for all test methods in the class):
#RunWith(SpringRunner.class)
#SpringBootTest
public class MyTestClass {
#IfProfileValue(name = "os.name", values = {"Linux"})
#Test
public void testMe() {
// will run only in linux, otherwise
// won't even try to load an
// application context
....
}
}
This covers the case when you resolve the VPN connectivity externally and run the tests with a property. However, if you want to implement the VPN connectivity check in java, this annotation along not enough because it can work only with Java system properties, so in order to work with custom logic you need to implement org.springframework.test.annotation.ProfileValueSource:
public class VPNConnectivityProfileValueSource implements ProfileValueSource {
private String vpnEnabled = "true";
public VPNConnectivityProfileValueSource () {
// no spring context is available here
ClassPathResource resource = new ClassPathResource("vpn-config.properties");
if (resource.exists()) {
// read the VPN address,
//
//this.testProps = PropertiesLoaderUtils.loadProperties(resource);
// invoke your utility, check the connectivity, etc.
this.vpnEnabled = ...
}
}
#Override
public String get(String key) {
// this is important method,
if(key.equals("vpn.enabled") {
return this.vpnEnabled;
}
else return System.getProperty(key);
}
}
The last thing is to make the test aware of the ProfileValueSource:
For this there is another special annotation that you put on the test:
#ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)
All in all it the test can look like this:
#RunWith(SpringRunner.class)
#SpringBootTest
#ProfileValueSourceConfiguration(VPNConnectivityProfileValueSource.class)
#IfProfileValue(name = "vpn.enabled", value = "true")
public class MyTestClass {
#Test
public void testMe() {
....
}
}
All the classes/annotations I've mentioned reside in package org.springframework.test.annotation

How to populate test data before #PostConstuct?

I'm working on an application that needs to run up a TCP server as one of the first things it does. This is currently initiated via a Spring config class:
#PostConstruct
public void initTCPServer() {
// Gets the port number and other values from the database...
// Note: This uses dependency injection of the Data Access Objects (DAOs).
}
It works fine when using an existing pre-populated database but I'm running into problems when trying to write an integration test: The data needs to be pre-populated but the #PostConstruct is firing before the data population if it is in a JUnit #Before method and similarly when using SpringJUnit4ClassRunner's #TestExecutionListeners.
The only solution I can think of now is to initialise data in a test config class with a #PostConstruct and ensure this is loaded first - but this seems a bit dirty. EDIT: Just tried and this failed with a BeanCurrentlyInCreationException - looks like dependency injection of the EntityManagerFactory hadn't finished.
Is there a more elegant way (e.g. should I be running up the TCP server somewhere else, i.e. not managed by Spring)?

Testing Hibernate DAO using Junit

I am using a combination of Spring and Hibernate in my project and would like to test the DAO methods like Save and Delete methods.
daoFoundation is a wrapper class created over hibernateSession.
#Override
public String createSubject(Subject subject) {
String subjectId = (String) daoFoundation.save(subject);
return subjectId;
}
This is what I wrote in my JUnit Runs with SpringJunit4ClassRunner
I created the subject object in my SetupMethod.
#Test
public void createSubjectTest(){
subjectDao.createSubject(subject);
assertNotNull(hassSubjectSelection.getId());
}
Is this sufficient or do I need to write anything additional in my test class?
The easiest way is to import your Spring application context, autowire in the DAO's you want to test and then mark either your test methods or the entire class as #Transactional. This will create a Hibernate session, run your test and then automatically roll back the transaction so you don't effect your database state with your tests.
Have a look at how to run unit tests with Spring here. You can get Spring to create your entire application context using the #ContextConfiguration annotation. So if you create your database using an XML file called database-servlet.xml then you would annotate
#ContextConfiguration(locations={"classpath:/database-servlet.xml"})
public class Test()
You can use the annotation #RunWith(SpringJUnit4ClassRunner.class) to use functionality of the Spring TestContext Framework with your unit tests. This allows you to do things like declare expected exceptions that should be thrown, run timed tests, repeat test runs X times and a bunch of other cool stuff.
Basically to get this working, your test class should look similar to the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={YOUR APP CONTEXT FILES HERE AS A COMMA SEPARATED LIST})
public class Test(){
#Autowired
private YourDAO yourDAO;
#Test
#Transactional
public void testSave(){
//Test save method here. Any database changes made here will be
//automatically rolled back when the test finishes.
}
Let me know if that works or not.
The best way to test your dao layer is to use the spring jdbctemplate to write data to your database the test your get and delete methods. Then in the #after delete the records you wrote. Then use hibernate to write to your database and use jdbctemplate to read them back. Then delete your test rows. Anything less and all you are really doing is testing hibernate's caching.

Categories

Resources