Как воссоздать базу данных перед каждым тестом в Spring?
My Spring -Boot-Mvc-Web-приложение имеет следующую конфигурацию базы данных в файле application.properties
:
spring.datasource.url=jdbc:h2:tcp://localhost/~/pdk
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver
Это единственная конфигурация, которую я сделал. Никаких других конфигураций, сделанных мной нигде. Тем не менее Spring и подсистемы автоматически воссоздают базу данных при каждом запуске веб-приложения. База данных воссоздается именно при запуске системы, пока она содержит данные после завершения приложения.
Я не понимал эти значения по умолчанию и ожидал, что это подходит для тестов.
Но когда я начал запускать тесты, я обнаружил, что база данных воссоздана только один раз. Поскольку тесты выполняются без предопределенного порядка, это вообще бессмысленно.
Итак, вопрос: как сделать какой-то смысл? I.e., как сделать базу данных воссозданной перед каждым тестом, как это происходит при первом запуске приложения?
Заголовок моего тестового класса:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = myapp.class)
//@WebAppConfiguration
@WebIntegrationTest
@DirtiesContext
public class WebControllersTest {
Как вы видите, я пробовал @DirtiesContext
на уровне класса, и это не помогло.
UPDATE
У меня есть bean
@Service
public class DatabaseService implements InitializingBean {
который имеет метод
@Override
@Transactional()
public void afterPropertiesSet() throws Exception {
log.info("Bootstrapping data...");
User user = createRootUser();
if(populateDemo) {
populateDemos();
}
log.info("...Bootstrapping completed");
}
Теперь я применил метод populateDemos()
для очистки всех данных из базы данных. К сожалению, он не вызывается перед каждым тестом, несмотря на @DirtiesContext
. Почему?
Ответы
Ответ 1
На самом деле, я думаю, вы этого хотите:
@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 может использоваться как уровень уровня и уровня метода аннотации в пределах одного класса. В таких сценариях ApplicationContext будет помечен как грязный после любого такого аннотированного метод, а также после всего класса. Если DirtiesContext.ClassMode устанавливается в AFTER_EACH_TEST_METHOD, контекст будет отмечен как загрязненный после каждого метода тестирования в классе.
Ответ 2
Чтобы создать базу данных, вы должны делать то, что говорят другие ответы с помощью spring.jpa.hibernate.ddl-auto=create-drop
, теперь, если ваше намерение состоит в том, чтобы окупить базу данных для каждого теста, тогда spring обеспечивает очень полезную анотацию
@Transactional(value=JpaConfiguration.TRANSACTION_MANAGER_NAME)
@Sql(executionPhase=ExecutionPhase.BEFORE_TEST_METHOD,scripts="classpath:/test-sql/group2.sql")
public class GroupServiceTest extends TimeoffApplicationTests {
который из этого пакета org.springframework.test.context.jdbc.Sql;
, и вы можете запустить метод перед тестом и метод после теста. Чтобы заполнить базу данных.
Что касается создания базы данных каждый раз, скажите, что вы хотите, чтобы в вашем тесте была опция create-drop, вы можете настроить свои тесты с помощью специальных свойств с помощью этой аннотации
@TestPropertySource(locations="classpath:application-test.properties")
public class TimeoffApplicationTests extends AbstractTransactionalJUnit4SpringContextTests{
Надеюсь, что это поможет
Ответ 3
При весенней загрузке базу данных h2 можно определить уникально для каждого теста. Просто переопределите URL источника данных для каждого теста
@SpringBootTest(properties = {"spring.config.name=myapp-test-h2","myapp.trx.datasource.url=jdbc:h2:mem:trxServiceStatus"})
Тесты могут выполняться параллельно.
В рамках теста данные могут быть сброшены
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
Ответ 4
Если вы используете spring.jpa.hibernate.ddl-auto=create-drop
, должно быть достаточно для создания/удаления базы данных?
Ответ 5
Если вы ищете альтернативу для @DirtiesContext
, этот код ниже поможет вам. Я использовал код из этого ответа.
Сначала настройте базу данных H2 для файла application.yml
в папке с тестовыми ресурсами:
spring:
datasource:
platform: h2
url: jdbc:h2:mem:test
driver-class-name: org.h2.Driver
username: sa
password:
После этого создайте класс с именем 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();
}
}
Приведенный выше код сбросит базу данных (усеченные таблицы, последовательности сброса и т.д.) и готов к работе с базой данных H2. Если вы используете другую базу данных памяти (например, HsqlDB), вам нужно внести необходимые изменения в запросы SQLs, чтобы выполнить то же самое.
После этого перейдите в свой тестовый класс и добавьте аннотацию @TestExecutionListeners
, например:
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {ResetDatabaseTestExecutionListener.class}
)
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CreateOrderIT {
Это должно работать.
Если вы не видите никакой разницы в производительности между этим подходом и @DirtiesContext
, , вероятно, вы используете @MockBean
внутри своих тестов, что помечает контекст как грязный и автоматически перезагружает контекст Spring.
Ответ 6
Если вы не используете какую-то Spring -Data-интеграцию (которой я вообще не знаю), это похоже на пользовательскую логику, которую вам нужно реализовать самостоятельно. Spring не знает о ваших базах данных, его схемах и таблицах.
Предполагая JUnit, напишите подходящие методы @Before
и @After
для настройки и очистки базы данных, ее таблиц и данных. Ваши тесты сами могут записать нужные им данные и, возможно, потенциально очистить их после себя.