I have some integration tests that create/delete entries in my database. The problem is if I run the tests with
spring.profiles.active=prod
my production database gets deleted (as the tests clear the database). Is there any way to prevent tests from running on this specific spring profile?
I have seen this thread: How to prevent running tests when specific spring profile is active? but there was no useful answer.
Thank you
There could be multiple solution to your problem.
Using in-memory databases like H2 for Sql and flapdoodle for no-sql for running tests. **preferred way
Create a separate properties file with clone of spring properties. Just change the database properties/spring profile or other things. Use this properties file with #testpropertysource on test class.
Use #dirtiescontext on tests to create/delete impacted rows only.
Another thing you can do is create stud classes your database layer to mock operations.
I was able to resolve the problem following this comment: https://stackoverflow.com/a/32892291/8679100. The solution is not perfect, as I need to verify if the prod profile is active in #BeforeAll and #AfterAll, but it does work. Furthermore, System.getProperty("spring.profiles.active", "");didn't actually work, butArrays.stream(environment.getActiveProfiles()).anyMatch(env -> (env.equalsIgnoreCase("prod")))``` did
You can use #IfProfileValue annotation to disable the test. it's not directly depend on the spring profile, but you can easily use config file to set the value you want based on the spring profile.
Having said that - it sounds very risky to run tests that delete entries from the db (or any other db transaction) on production DB. I support what #Sachin suggested above - run your test on tests environment, not production
Related
I'm trying to test the service which updates multiple tables from the database and I want to rollback the database to previous state after each test case. All solutions I have found are using #Transactional and #Rollback from Spring framework, but since my application is not a Spring web application, I would like to use javax #Transactional, which does not work for me.
Is this possible with javax at all or anything else except the Spring?
Rollback a transaction isn't a good idea for test (integration test) as the constraint may not be validated before the commit.
You should:
have a DB only for integration tests (or an embedded db or a container db or in RAM db)
execute, for example in a class rule or in a test rule, script SQL in order to bring the db in a known status
execute a test
if test modifies the db then run a truncate of tables modified (again or in your class or test rule) and, before peform a new test, run again the script at point 2
run integration tests not so often as unit tests
Better idea is to use in-memory database:
H2 https://www.h2database.com/
Recommendation for a Java in memory database
Not always and not everything in database can be rolled-back to initial state ( ex. sequences ).
Is there a way to configure the H2 Compatibility Mode for the H2 Database that Spring Boot can auto configure to replace your regular database without just replacing it?
There are documented ways of disabling the autoconfiguration test database replacement:
https://stackoverflow.com/a/43557541/141042
I don't mind doing something like this, but most of the alternatives come with other complexities:
if you add a application.properties in your test classpath, this replaces your main application.properties during test runs, so then you're stuck maintaining two files (e.g. https://github.com/spring-projects/spring-boot/issues/10271)
if you set up a profile for test runs, then you have to make sure that any test needing the test database is marked with the profile
Is there a better way of doing this? I like the simplicity of the Spring Boot auto configured test database, but it seems like I have to force it into MySQL compatibility mode now to continue to work with my existing migrations.
So is there:
a way to configure the compatibility mode of h2 when spring autoconfigures the test database without disabling that mechanism?
a way of specifying the jdbc url for all tests without having to modify each test (e.g. to include a profile) or maintaining two application property files (e.g. a new application.properties in src/test/resources)
There isn't an option to set a custom URL for the embedded datasource that Spring Boot replaces in your tests. We offer a way to specify which connection type you want but that doesn't include the URL itself. I have no idea how easy we could add that but it's worth looking at least, I've created issue #19038
As for specifying the URL, you shouldn't add an application.properties in your test classpath for the reason you've mentioned. The SO thread you've referenced already has an answer that refers to application-test.properties.
I am trying to implement integration tests for my Tomcat application, but my issue is that the application is launched separately from the tests so the tests cannot access the application context and neither the database.
My idea is running the tests "within" the running application, so I can #Autowire EntityManager and check for instance the state of the database during testing or even create database entities for testing.
My only idea of doing this is to actually run the application programmatically from the tests as ClassPathXmlApplicationContext("applicationContext.xml") and the access the Context. This would work, but it would be very hard for debugging as we wouldn't be able to use Hotswapping during the testing. Also I guess the server would be stopped as soon as the tests would end. I guess that is not the best and correct solution.
EDIT:
My question was probably unclear, so I will try to clarify.
I have a Tomcat application with Spring and Hibernate. The Spring beans and Hibernate database connection is initialised when the Tomcat application is started. The issue is how to run the tests of the active Spring beans from methods annotated with #Test in src/test/java which are started separately.
Consider this class:
#Component
class MyRepository {
#Autowired
EntityManager em;
#Transactional
public void myMethod(MyEntity entity) {
// do some job with entity
...
em.flush();
}
}
This class will be initialised with Tomcat as a MyRepository bean.
To test it, I cannot just call new MyRepository().myMethod(...) - I need to access the bean. The issue is accessing the bean from the #Test method:
#Test
void testMyRepository() {
Item item = ...
// then use the repository to handle the entity
context.getBean(MyRepository.class).myMethod(item);
// then assert the state of the database
context.getBean(EntityManager.class).find(Item.class, ...) ...
}
I can probably get the context in the initialisation of the tests with
ApplicationContext context = ClassPathXmlApplicationContext("applicationContext.xml");
But it would mean launching the whole application each time the tests are started. The better solution would be if the application could run separately from the tests.
Hope my problem is more clear now.
I would suggest you to use the SpringRunner to start the Spring application context and perform your tests on that running instance. You can customize the context the way it doesn't contain parts you don't want to tests and you can create mocks for components that require some external resources (REST clients and such). Take a look at the Spring docs or Spring Boot docs.
If multiple tests use the same Spring context configuration, the context is started just once and reused. So it's good to have it's configuration in a parent class of your tests. You can autowire any Spring bean into your test and test it.
You can use an in-memory database (such as H2) instead of a production one, so your tests are not dependent on an external infrastructure. To initialize the database, use tools like Flyway or Liquibase. To clear the database before each test, you can use the #Sql annotation.
You can find many examples of projects with such tests, for example my own demo.
If you want to test an external system, I would suggest something like JMeter.
Unfortunately you cant mirror your classes and use them in your tests. Thats a big disadvantage of web services. They always depend on user / machine interaction. With a lot of effort you can extract the functionality of the essential classes or methods and construct test scenarios etc. with jUnit.
The Overview of your possibilities:
special drivers and placeholders
you can use a logger with detailed log-level and file output. Then you created scenarios with the expected result and compare it with your log files.
Capture replay tools. They record your exection and replay them for monitoring.
I can also recommend using Selenium for the frontend tests.
Hope it helped.
There were some proposed solutions to the question "How to test SQL statements in an application" -
Using RAM memory - I can't change the configuration of staging environment where testing happens.
Using H2 - Not very compatible even in PostgreSQL mode
Use the same database to run the tests.
Using in-memory mode - PostgreSQL doesn't have one.
The third one was viable and I looked into Test Containers which is actually a beautiful solution but a relatively new one. As a result, our company is sceptical of adopting it.
We use Mybatis to access PostgreSQL.
Another way would be to recreate entire schema and populate required tables before tests. Here is the problem, I could create and delete schema with tables with the same name. To avoid name collision I'd have to change schema's name, as a result, even queries should be renamed which is not at all preferred. Is there a way to do this without changing queries but pointing them to the dummy schema.
You should NOT change your queries. In tests you should only change the connection url your application will use. The problem is, how to get that url working.
To have full test coverage you need the same db (as you noticed, h2 and other in-memory db are not very compatible). postgres doesn't have in-memory mode so you have to manage the lifecycle yourself. there is a few decisions you have to make. some of them:
where will you get the db from: require all the devs to provide postgres (installation / docker / vagrant) or automate the setup?
how to prepare db for tests: manual schema setup and cleanup?
how to reset db between tests: restart? always rollback? predefined and separately defined content? some kind of reverse operations?
if and how to make those tests fast?
there are some tools that can help you solve some of the problems:
testcontainers will help you provide
db.
dbunit - will help you prepare data for your test.
cons:
a lot of work is required to create and maintain schema and data. especially when your project is in a intensive development stage.
it's another abstraction layer so if suddenly you want to use some db feature that is unsupported by this tool, it may be difficult to test it
testegration - intents to provide you full, ready to use and extensible lifecycle (disclosure: i'm a creator).
cons:
free only for small projects
very young project
you can also fill the gaps on your own. as always it's a trade: time vs money
you can define database configuration for test purpose and connect to your real database base for execute tests. you should access to test database configuration in test classes.
for example, if you use spring and hibernate to connect to the database, you can define a test hibernate configuration xml file where it connect to test database. then in your test classes, use this configuration file as follow:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguratiion({testHibernate.xml, testSpring.xml , .... })
#TestExecutionListeners({...})
public class TestClass {
....
#Test
public void test1(){
...
}
}
so, you can access your test hibernate session factory to execute your queries.
I use liquibase to set up my database schema. I disable hibernate to create anything. Because of that, my import.sql is ignored. Is there any way to configure spring boot or liquibase or any other part to load test data after liquibase has created the tables?
If you need something crude (i.e, not for actual data migrations), you can use Spring's JDBC initializer in Spring Boot. In a nutshell, you'll need to:
create a data-test.sql to load your data, and place it in your src/main/resources directory. For different environments, just use the naming convention data-{platform}.sql
add applications-test.properties to src/main/resources with the following:
spring.datasource.platform=test # this actives data-test.sql above
spring.datasource.continueOnError=???? # depends on your needs
to active the application-test.properties during your testing, make sure "test" is one of the profiles that's active during your integration test. One way to do this is to annotate your test class with #ActiveProfiles({"test", ...}).
The simplest way seems to load the data with liquibase. You can do it with a normal Changeset (XML or JSON) or a Changeset in SQL-Format.
The most common way is to load CSV-Data or run an existing SQL-File.