问题
My Spring-Boot-Mvc-Web application has the following database configuration in application.properties
file:
spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
this is the only config I made. No any other configurations made by me anywhere. Nevertheless the Spring and subsystems are automatically recreate database on each web application run. Database is recreated namely on system run while it contains data after application ends.
I was not understanding this defaults and was expecting this is suitable for tests.
But when I started to run tests I found that database is recreated only once. Since tests are executed at no predefined order, this is senseless at all.
So, the question is: how to make any sense? I.e. how to make database recreate before each test as it happens at application first start?
My test class header is follows:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {
As you see, I tried @DirtiesContext
at class level and it didn't help.
UPDATE
I have a bean
@Service
public class DatabaseService implements InitializingBean {
which has a method
@Override
@Transactional()
public void afterPropertiesSet() throws Exception {
log.info("Bootstrapping data...");
User user = createRootUser();
if(populateDemo) {
populateDemos();
}
log.info("...Bootstrapping completed");
}
Now I made it's populateDemos()
method to clear all data from database. Unfortunately, it does not called before each test despite @DirtiesContext
. Why?
回答1:
Actually, I think you want this:
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
http://docs.spring.io/autorepo/docs/spring-framework/4.2.6.RELEASE/javadoc-api/org/springframework/test/annotation/DirtiesContext.html
@DirtiesContext may be used as a class-level and method-level annotation within the same class. In such scenarios, the ApplicationContext will be marked as dirty after any such annotated method as well as after the entire class. If the DirtiesContext.ClassMode is set to AFTER_EACH_TEST_METHOD, the context will be marked dirty after each test method in the class.
回答2:
To create the database you have to do what the other answers say with the spring.jpa.hibernate.ddl-auto=create-drop
, now if your intent is to pupulate the database on each test then spring provides a very usefull anotation
@Transactional(value=JpaConfiguration.TRANSACTION_MANAGER_NAME)
@Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/test-sql/group2.sql")
public class GroupServiceTest extends TimeoffApplicationTests {
that is from this package org.springframework.test.context.jdbc.Sql;
and you can run a before test method and a after test method. To populate the database.
Regarding creating the database each time, Say you only want your Test to have the create-drop option you can configure your tests with a custom properties with this annotation
@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{
Hope it helps
回答3:
With spring boot the h2 database can be defined uniquely for each test. Just override the data source URL for each test
@SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})
The tests can run in parallel.
Within the test the data can be reset by
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
回答4:
If you use spring.jpa.hibernate.ddl-auto=create-drop
should be enough to create/drop database?
回答5:
If you are looking for an alternative for the @DirtiesContext
, this code below will help you. I used some code from this answer.
First, setup the H2 database on the application.yml
file on your test resources folder:
spring:
datasource:
platform: h2
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
username: sa
password:
After that, create a class called ResetDatabaseTestExecutionListener
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.HashSet;
import java.util.Set;
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
@Autowired
private DataSource dataSource;
public final int getOrder() {
return 2001;
}
private boolean alreadyCleared = false;
@Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!alreadyCleared) {
cleanupDatabase();
alreadyCleared = true;
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
Connection c = dataSource.getConnection();
Statement s = c.createStatement();
// Disable FK
s.execute("SET REFERENTIAL_INTEGRITY FALSE");
// Find all tables and truncate them
Set<String> tables = new HashSet<>();
ResultSet rs = s.executeQuery("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='PUBLIC'");
while (rs.next()) {
tables.add(rs.getString(1));
}
rs.close();
for (String table : tables) {
s.executeUpdate("TRUNCATE TABLE " + table);
}
// Idem for sequences
Set<String> sequences = new HashSet<>();
rs = s.executeQuery("SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='PUBLIC'");
while (rs.next()) {
sequences.add(rs.getString(1));
}
rs.close();
for (String seq : sequences) {
s.executeUpdate("ALTER SEQUENCE " + seq + " RESTART WITH 1");
}
// Enable FK
s.execute("SET REFERENTIAL_INTEGRITY TRUE");
s.close();
c.close();
}
}
The code above will reset the database (truncate tables, reset sequences, etc) and is prepared to work with H2 database. If you are using another memory database (like HsqlDB) you need to make the necessary changes on the SQLs queries to accomplish the same thing.
After that, go to your test class and add the @TestExecutionListeners
annotation, like:
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {ResetDatabaseTestExecutionListener.class}
)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {
This should work.
If you not see any performance difference between this approach and @DirtiesContext
, probably you are using @MockBean
inside of your tests, what mark the context as dirty and automatically reload the Spring context.
回答6:
Unless you're using some kind of Spring-Data integration (which I don't know at all), this seems like custom logic you'll need to implement yourself. Spring doesn't know about your databases, its schemas, and tables.
Assuming JUnit, write appropriate @Before
and @After
methods to set up and clean up your database, its tables, and data. Your tests can themselves write the data they need, and potentially clean up after themselves if appropriate.
回答7:
Using the accepted answer in Spring-Boot 2.2.0, I was seeing JDBC syntax errors related to constraints:
Caused by: org.h2.jdbc.JdbcSQLSyntaxErrorException: Constraint "FKEFFD698EA2E75FXEERWBO8IUT" already exists; SQL statement: alter table foo add constraint FKeffd698ea2e75fxeerwbo8iut foreign key (bar) references bar [90045-200]
To fix this, I added @AutoConfiguredTestDatabase
to my unit test:
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.annotation.DirtiesContext.ClassMode;
import org.springframework.boot.test.context.SpringBootTest;
import org.junit.runner.RunWith;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@SpringBootTest
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
@AutoConfigureTestDatabase(replace = Replace.ANY)
public class FooRepositoryTest { ... }
回答8:
You can annotate your test class with @Transactional
:
import org.springframework.transaction.annotation.Transactional;
...
...
@RunWith(SpringRunner.class)
@Transactional
public class MyClassTest {
@Autowired
private SomeRepository repository;
@Before
public void init() {
// add some test data, that data would be rolled back, and recreated for each separate test
repository.save(...);
}
@Test
public void testSomething() {
// add some more data
repository.save(...);
// update some base data
repository.delete(...);
// all the changes on database done in that test would be rolled back after test finish
}
}
All tests are wrapped inside a transaction, that is rolled back at the end of each test. There are unfortunately some problems with that annotation of course, and you need to pay special attention, when for example your production code uses transactions with different score.
来源:https://stackoverflow.com/questions/34617152/how-to-re-create-database-before-each-test-in-spring