
В качестве основной СУБД в моем проекте используется postgres, но в тестах мы будем использовать in-memory БД h2. В этом случае нам не потребуется отдельной БД и все данные будут в оперативной памяти. После тестов не надо что-то подчищать.
Необходимые зависимости:
<!-- assertj-core нужен только для assert в тестах, вы можете использовать и аналоги-->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.5.3</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.springtestdbunit</groupId>
<artifactId>spring-test-dbunit</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>
<!-- in memory DB -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.194</version>
<scope>test</scope>
</dependency> |
<!-- assertj-core нужен только для assert в тестах, вы можете использовать и аналоги-->
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>3.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.3.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>2.5.3</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>com.github.springtestdbunit</groupId>
<artifactId>spring-test-dbunit</artifactId>
<version>1.3.0</version>
<scope>test</scope>
</dependency>
<!-- in memory DB -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.194</version>
<scope>test</scope>
</dependency>
Проперти файл с настройками БД:
jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:mem:<название БД>;INIT=RUNSCRIPT FROM 'classpath:init.sql';DB_CLOSE_DELAY=-1
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto=create
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.connection.release_mode=on_close
...
(специфичные настройки для c3p0 и ehcache) |
jdbc.driverClassName=org.h2.Driver
jdbc.url=jdbc:h2:mem:<название БД>;INIT=RUNSCRIPT FROM 'classpath:init.sql';DB_CLOSE_DELAY=-1
hibernate.dialect=org.hibernate.dialect.H2Dialect
hibernate.hbm2ddl.auto=create
hibernate.show_sql=true
hibernate.format_sql=true
hibernate.connection.release_mode=on_close
...
(специфичные настройки для c3p0 и ehcache)
Обратите внимание на строку jdbc.url: в ней указан скрипт init.sql, который будет вызван перед запуском тестов.
А это тестовый конфиг:
@Configuration
@EnableJpaRepositories(basePackages = {"ru.outofrange.repo"})
@ComponentScan(value = {"ru.outofrange.service"})
@PropertySource({"hibernate_test.properties", "classpath:application.properties"})
@Import({LibraryConfig.class})
@EnableTransactionManagement
public class PersisitenceTestConfig {
@Autowired
private Environment environment;
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getProperty("jdbc.url"));
dataSource.setUsername(environment.getProperty("jdbc.user"));
dataSource.setPassword(environment.getProperty("jdbc.pass"));
return dataSource;
}
@Bean
public H2Connection h2Connection() throws SQLException, DatabaseUnitException {
return new H2Connection(dataSource().getConnection(), "<schema name>");
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
...
} |
@Configuration
@EnableJpaRepositories(basePackages = {"ru.outofrange.repo"})
@ComponentScan(value = {"ru.outofrange.service"})
@PropertySource({"hibernate_test.properties", "classpath:application.properties"})
@Import({LibraryConfig.class})
@EnableTransactionManagement
public class PersisitenceTestConfig {
@Autowired
private Environment environment;
@Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(environment.getProperty("jdbc.driverClassName"));
dataSource.setUrl(environment.getProperty("jdbc.url"));
dataSource.setUsername(environment.getProperty("jdbc.user"));
dataSource.setPassword(environment.getProperty("jdbc.pass"));
return dataSource;
}
@Bean
public H2Connection h2Connection() throws SQLException, DatabaseUnitException {
return new H2Connection(dataSource().getConnection(), "<schema name>");
}
@Bean
public JpaTransactionManager transactionManager(EntityManagerFactory emf) {
return new JpaTransactionManager(emf);
}
@Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
...
}
Содержимое скрипта init.sql:
CREATE SCHEMA IF NOT EXISTS <schema name>;
DROP ALIAS IF EXISTS <schema name>.custom_to_number;
CREATE ALIAS <schema name>.custom_to_number AS $$
Long customToNumber(String str) {
return Long.valueOf(str);
}
$$; |
CREATE SCHEMA IF NOT EXISTS <schema name>;
DROP ALIAS IF EXISTS <schema name>.custom_to_number;
CREATE ALIAS <schema name>.custom_to_number AS $$
Long customToNumber(String str) {
return Long.valueOf(str);
}
$$;
Здесь создается функция custom_to_number, которая принимает строку (ожидается, что в ней десятичное число) в качестве параметра, и выдает это число. Эта функция рассматривалась тут:
статья. Для совместимости с кодом, который будет тестироваться, нужно такое же имя схемы.
Собственно, тестирующий класс:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersisitenceTestConfig.class},
loader = AnnotationConfigContextLoader.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
@DbUnitConfiguration(databaseConnection = "h2Connection")
@DatabaseSetup("classpath:claim_search_dataset.xml")
public class ClaimServiceTestIT {
@Autowired
ClaimRepo claimRepo;
@Autowired
ClaimService claimService;
@Test
public void testClaimSearch() {
} |
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersisitenceTestConfig.class},
loader = AnnotationConfigContextLoader.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
@DbUnitConfiguration(databaseConnection = "h2Connection")
@DatabaseSetup("classpath:claim_search_dataset.xml")
public class ClaimServiceTestIT {
@Autowired
ClaimRepo claimRepo;
@Autowired
ClaimService claimService;
@Test
public void testClaimSearch() {
}
Из специфичных аннотаций здесь
@DbUnitConfiguration(databaseConnection = "h2Connection") - указываем имя бина, который создали ранее в конфиге. И
@DatabaseSetup("classpath:claim_search_dataset.xml") - в нем описаны данные, которые будут размещены в БД.
Данные на вход
dbunit такие:
<dataset>
<TOPIC_TYPES id="1" officetype="type 1"/>
<TOPIC_TYPES id="2" officetype="type 2"/>
<TOPIC_TYPES id="3" officetype="type 3"/>
<TOPIC_TYPES id="4" officetype="type 4"/>
<TOPIC_TYPES id="5" officetype="type 5"/>
<TOPICS id="1" author_id="1" type_id="1" status_id="2" version="0"/>
<TOPICS id="2" author_id="2" type_id="3" status_id="2" version="0"/>
<TOPICS id="3" author_id="3" type_id="2" status_id="2" version="0"/>
<TOPICS id="20" author_id="4" type_id="4" status_id="1" version="0"/>
</dataset> |
<dataset>
<TOPIC_TYPES id="1" officetype="type 1"/>
<TOPIC_TYPES id="2" officetype="type 2"/>
<TOPIC_TYPES id="3" officetype="type 3"/>
<TOPIC_TYPES id="4" officetype="type 4"/>
<TOPIC_TYPES id="5" officetype="type 5"/>
<TOPICS id="1" author_id="1" type_id="1" status_id="2" version="0"/>
<TOPICS id="2" author_id="2" type_id="3" status_id="2" version="0"/>
<TOPICS id="3" author_id="3" type_id="2" status_id="2" version="0"/>
<TOPICS id="20" author_id="4" type_id="4" status_id="1" version="0"/>
</dataset>
Названия таблиц не чувствительны к регистру. Структуру таблиц у меня создает сам hibernate, т.к. я указал:
hibernate.hbm2ddl.auto=create |
hibernate.hbm2ddl.auto=create
Ну и, собственно, тестирующий класс:
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersisitenceTestConfig.class},
loader = AnnotationConfigContextLoader.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
@DbUnitConfiguration(databaseConnection = "h2Connection")
@DatabaseSetup("classpath:claim_search_dataset.xml")
public class ClaimServiceTestIT {
@Autowired
TopicRepo topicRepo;
@Autowired
TopicService TopicService;
@Test
public void testTopicSearch() {
Topic topic1 = topicRepo.findOne(1l);
Topic topic2 = topicRepo.findOne(2l);
Topic topic20 = topicRepo.findOne20l);
...
List<Topic> topiscs = topicService.findBySomeCriteria(...);
assertThat(claims)
.hasSize(3)
.doesNotContainNull()
.contains(topic1)
.contains(topic2)
.contains(topic20);
}
} |
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {PersisitenceTestConfig.class},
loader = AnnotationConfigContextLoader.class)
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
DbUnitTestExecutionListener.class})
@DbUnitConfiguration(databaseConnection = "h2Connection")
@DatabaseSetup("classpath:claim_search_dataset.xml")
public class ClaimServiceTestIT {
@Autowired
TopicRepo topicRepo;
@Autowired
TopicService TopicService;
@Test
public void testTopicSearch() {
Topic topic1 = topicRepo.findOne(1l);
Topic topic2 = topicRepo.findOne(2l);
Topic topic20 = topicRepo.findOne20l);
...
List<Topic> topiscs = topicService.findBySomeCriteria(...);
assertThat(claims)
.hasSize(3)
.doesNotContainNull()
.contains(topic1)
.contains(topic2)
.contains(topic20);
}
}
Короче говоря, ваши тесты теперь могут проверять содержимое БД. При этом можно дергать методы служб и менять содержимое таблиц точно так же, как это происходит в коде приложения.