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?
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.
You put it on your Test class.
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 #AutoConfigureTestDatabase to my unit test (part of spring-boot-test-autoconfigure):
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 { ... }
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
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 in your tests, what marks the Spring context as dirty and automatically reload the entire context.
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)
There is library that covers "reset H2 database" feature in JUnit 5 tests:
https://github.com/cronn/test-utils#h2util
Sample usage:
#ExtendWith(SpringExtension.class)
#Import(H2Util.class)
class MyTest {
#BeforeEach
void resetDatabase(#Autowired H2Util h2Util) {
h2Util.resetDatabase();
}
// tests...
}
Maven coords:
<dependency>
<groupId>de.cronn</groupId>
<artifactId>test-utils</artifactId>
<version>0.2.0</version>
<scope>test</scope>
</dependency>
Disclaimer: I’m the author of suggested library.
If you use spring.jpa.hibernate.ddl-auto=create-drop should be enough to create/drop database?
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.
A solution using try/resources and a configurable schema based on this answer. Our trouble was that our H2 database leaked data between test cases. So this Listener fires before each test method.
The Listener:
public class ResetDatabaseTestExecutionListener extends AbstractTestExecutionListener {
private static final List<String> IGNORED_TABLES = List.of(
"TABLE_A",
"TABLE_B"
);
private static final String SQL_DISABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY FALSE";
private static final String SQL_ENABLE_REFERENTIAL_INTEGRITY = "SET REFERENTIAL_INTEGRITY TRUE";
private static final String SQL_FIND_TABLE_NAMES = "SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='%s'";
private static final String SQL_TRUNCATE_TABLE = "TRUNCATE TABLE %s.%s";
private static final String SQL_FIND_SEQUENCE_NAMES = "SELECT SEQUENCE_NAME FROM INFORMATION_SCHEMA.SEQUENCES WHERE SEQUENCE_SCHEMA='%s'";
private static final String SQL_RESTART_SEQUENCE = "ALTER SEQUENCE %s.%s RESTART WITH 1";
#Autowired
private DataSource dataSource;
#Value("${schema.property}")
private String schema;
#Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
#Override
public void beforeTestMethod(TestContext testContext) throws Exception {
cleanupDatabase();
}
private void cleanupDatabase() throws SQLException {
try (
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()
) {
statement.execute(SQL_DISABLE_REFERENTIAL_INTEGRITY);
Set<String> tables = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_TABLE_NAMES, schema))) {
while (resultSet.next()) {
tables.add(resultSet.getString(1));
}
}
for (String table : tables) {
if (!IGNORED_TABLES.contains(table)) {
statement.executeUpdate(String.format(SQL_TRUNCATE_TABLE, schema, table));
}
}
Set<String> sequences = new HashSet<>();
try (ResultSet resultSet = statement.executeQuery(String.format(SQL_FIND_SEQUENCE_NAMES, schema))) {
while (resultSet.next()) {
sequences.add(resultSet.getString(1));
}
}
for (String sequence : sequences) {
statement.executeUpdate(String.format(SQL_RESTART_SEQUENCE, schema, sequence));
}
statement.execute(SQL_ENABLE_REFERENTIAL_INTEGRITY);
}
}
}
Using a custom annotation:
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = { ResetDatabaseTestExecutionListener.class }
)
public #interface ResetDatabase {
}
You can easily mark each test in which you want to reset the database:
#SpringBootTest(
webEnvironment = RANDOM_PORT,
classes = { Application.class }
)
#ResetDatabase
public class SomeClassIT {
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.
You could also try out https://www.testcontainers.org/ which helps you to run databases inside containers and you can create a fresh database for each test run too. It will be very slow though, since each time a container has to be created and the database server has to be started, configured and then migrations have to be run, then the test can be executed.
Nothing worked for me, but the following:
For every test class you can put the following annotations:
#TestMethodOrder(MethodOrderer.OrderAnnotation.class) //in case you need tests to be in specific order
#DataJpaTest // will disable full auto-configuration and instead apply only configuration relevant to JPA tests
#AutoConfigureTestDatabase(replace = NONE) //configures a test database to use instead of the application-defined or auto-configured DataSource
To order specific tests within the class you have to put also #Order annotation:
#Test
#Order(1) //first test
#Test
#Order(2) //second test, etc.
Rerunning the tests will not fail because of previous manipulations with db.
Related
I have a sql file which looks like this:
--Testdata
INSERT into cms_patch.configuration_main(document_key, config)
values('TEST/PhoenixTest', '{"b" : "c"}');
I am trying to run a test and want to seed this data to the table before that test, but the data is getting rolled back before the test happens.
Test class:
#Sql({"/sql/cms_dao_data.sql"})
public class CmsDaoImplTest extends AbstractPhoenixDao {
#Autowired
private CmsDao cmsDao;
#BeforeMethod(alwaysRun = true)
public void beforeMethod() {
this.cmsDao = new CmsDaoImpl();
}
#Test
#Sql("/sql/cms_dao_data.sql")
public void containsDocumentKey() {
Boolean result = cmsDao.containsDocumentKey("TEST/PhoenixTest");
assertTrue(result);
}
}
abstract class:
#Transactional
#TestPropertySource({"classpath:application-test.properties"})
#ContextConfiguration(classes = {RepositoryContext.class}, loader = AnnotationConfigContextLoader.class)
public abstract class AbstractPhoenixDao extends AbstractTransactionalTestNGSpringContextTests {
public AbstractPhoenixDao() {
}
}
Can someone please help me to know why the data is getting rolled back before the test happens.
the data is getting created because if I keep 2 values in insert with same primary key the sql run at start gives an error.
we are using postgres.
I am learning Junit 5 and test cases.
I am using spring boot version '2.2.6.RELEASE and JUnit 5,
in my application, I have a method that processes based on the boolean flag from property file.
\src\main\resources\application.properties
#data base connection properties
spring.app.datasource.url=jdbc:mysql://localhost:3306/student_db
spring.app.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.app.datasource.username=root
spring.datasource.password=root
spring.app.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect
#additional properties
spring.property.name=shrikant
spring.property.enable=false
database connection properties are used to create the database connection
Datasource.java
#Value("${spring.app.datasource.url}")
private String url;
#Value("${spring.app.datasource.driver-class-name}")
private String className;
#Value("${spring.app.datasource.username}")
private String userName;
#Value("${spring.datasource.password}")
private String password;
#Value("${spring.app.jpa.properties.hibernate.dialect}")
private String dialect;
controller class
#RestController
public class Controller {
#Value("${spring.property.name}")
private String name;
#Value("${spring.property.enable}")
private boolean status;
public void validateObject(String surName) {
if (status) { # if this flag is true then only process
System.out.println("name= " + name);
System.out.println("surName= " + surName);
}
}
ControllerTest.java
#SpringBootTest
class ControllerTest {
#Autowired
private Controller controller;
#Test
void show() {
controller.validateObject("sharma");
}
by default the flag is false, so every time test case runs it never processes the object.
so I tried to create aplication.properties in the test folder
\src\test\resources\application.properties
spring.property.name=vishal
spring.property.enable=true
but now it's giving me an error that
Could not resolve placeholder 'spring.app.datasource.url'
but I don't want to provide DB connection URL, I am not connecting to the database while testing.
Q1 - how to change the value of properties file for test case only.
Q2 - is it mandatory to provide all the keys of \src\main\resources\application.properties is \src\test\resources\application.properties?
I am new in test case, so little explained answers would be welcomed.
Update:-
I found that
#SpringBootTest
#TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"})
class ControllerTest {
will solve the issue temporarily, by providing keys along with values, but I have a lot of keys, which cannot be provided in such a way.
If you use #SpringBootTest then your test will load the whole Spring context. This means it will create all your beans and try to wire them together. If you inject property values to your beans, you have to specify them all for such tests as otherwise, you won't be able to boot the application.
What might help you in such a situation is to use test annotations like #WebMvcTest or #DataJpaTest to focus on testing just slices of your application. Using #WebMvcTest you'll get an application context just containing controllers and everything related to your web layer. Other beans (like service classes) can be mocked with #MockedBean.
Next, for testing business logic in service classes try not to use #SpringBootTest and rather rely on plain JUnit 5 and Mockito to verify your code.
You still might want to have some integration tests that use #SpringBootTest to make sure everything is working together. In such case, you can hard code any static property inside application.properties in src/test/resources/ or using the annotation like you already did: #TestPropertySource(properties = {"spring.property.name=vishal", " spring.property.status=true"}).
When it comes to providing a database, you can either configure an embedded database (which I would try to avoid) or use the same database as in production. Testcontainers helps you a lot when it comes to providing external infrastructure for your tests like a database.
An example setup with Spring Boot >= 2.2.6 and JUnit might look like the following:
#Testcontainers
#SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
public class ApplicationIT {
#Container
public static PostgreSQLContainer postgreSQLContainer = new PostgreSQLContainer()
.withPassword("password")
.withUsername("username");
#DynamicPropertySource
static void postgresqlProperties(DynamicPropertyRegistry registry) {
registry.add("spring.datasource.url", postgreSQLContainer::getJdbcUrl);
registry.add("spring.datasource.password", postgreSQLContainer::getPassword);
registry.add("spring.datasource.username", postgreSQLContainer::getUsername);
}
#Test
public void contextLoads() {
}
}
For Junit5, annotate your test class with path to application.properties in your src/main/resources:
#ExtendWith(SpringExtension.class)
#TestPropertySource("classpath:application.properties")
public class MyTest {
#Value("${property.name}")
private int myProperty;
tests...
}
property gets loaded
I want to run alters on the embedded derby database after a desktop application has been updated. To do this I attempted to create a JPS repository with my alter queries similar to this:
#Repository
public interface DatabaseUpdates {
#Modifying
#Query("alter table my_table add column my_column clob", nativeQuery = true)
void alterMyTableAddMyColumn();
}
And use it in the updater class like this
#Component
public class DatabaseUpdater {
#Autowired
private DatabaseUpdates databaseUpdates;
private void alterMyTable() {
// some logic that checks it the update needs to happen is here
databaseUpdates.alterMyTableAddMyColumn();
}
}
This throws NoSuchBeanDefinitionException: No qualifying bean of type DatabaseUpdates exceptions.
I also tried having DatabaseUpdates extend org.springframework.data.repository.Repository but then had problems with what to use as the domain type.
I am using spring-boot-starter-data-jpa 1.2.3.RELEASE.
Edit: This is to support a legacy app and I am looking for straight forward ways to support database updates. Database migrations tools are great but not in scope for this fix. I've been wishing this project had been using one for more than a month now and will be integrating one into the next version soon.
That sounds like an awful idea. There are frameworks, which were specifically developed for your usecase and are supported by Spring Boot, like Flyway and Liquibase. You are then able to specify your changes either in SQL or Java code (for Flyway) or in an XML, SQL, YAML or JSON file (for Liquibase). Those frameworks will check which migrations are necessary and they also keep track when a migration was executed.
I wouldn't put this in Spring JPA. If you mention it is temporary, maybe try some JDBC directly and run the queries on the database. Try to log the changes you make in a table (something similar to what liquibase is doing), so you don't run the queries multiple times.
Maybe try to run this before Spring builds your DataSource in your DatabaseConfiguration class.
#Configuration
#EnableJpaRepositories("")
#EnableTransactionManagement
public class DatabaseConfiguration implements EnvironmentAware {
#Bean(destroyMethod = "close")
public DataSource dataSource(DataSourceProperties dataSourceProperties, ApplicationProperties applicationProperties)
throws SQLException {
LOG.debug("Configuring Datasource");
//some code
alterSchema();
return new HikariDataSource(config);
}
private void alterSchema() {
// JDBC connection and queries to alter the schema
}
}
While not a best practice, you can temporarily use native JDBC, acquire connection from your existing DataSource. If you are only adding a column, make sure to check if the object(column) already exist.
Your repository can execute a transaction and build a native query with an EntityManager like the following code
import org.springframework.stereotype.Repository;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.transaction.Transactional;
#Repository
public class DatabaseUpdates{
#PersistenceContext
private EntityManager entityManager;
//ALTER TABLE `my_table` ADD COLUMN `my_column` VARCHAR(100) AFTER `after_column`;
#Transactional
public void alterMyTableAddMyColumn(String tableName, String columnName,
String columnType, String afterColumnName) {
String query = "ALTER TABLE `" + tableName + "` ADD COLUMN `" + columnName + "` " +
columnType + " AFTER `" + afterColumnName + "`";
entityManager.createNativeQuery(query).executeUpdate();
}
}
Your updater class calls your repository like this
import org.springframework.stereotype.Component;
import org.springframework.beans.factory.annotation.Autowired;
#Component
public class DatabaseUpdater {
#Autowired
private DatabaseUpdates databaseUpdates;
private void alterMyTable() {
// some logic that checks it the update needs to happen is here
String tableName = "my_table";
String columnName = "my_column";
String columnType = "VARCHAR(100)";
String afterColumnName = "after_column";
databaseUpdates.alterMyTableAddMyColumn(tableName, columnName,
columnType, afterColumnName);
}
}
tl;dr;. I have a method for creating new database table on the fly, and I want to write a unit test for it. Unfortunately, test runner does not perform rollback after tests in a proper way, and the table still remains in the DB after tests finished. What should I do?
Long story:
I am not very familiar neither with Java Persistence nor with Java Spring, so, if you found current solution ugly (as for me, it is rather ugly), please, tell me how to improve it - I will very appreciate your opinion.
I have a SysDatastoreService with the following implementation of addStaticDatastore method.
#Service
public class SysDatastoreServiceImpl implements SysDatastoreService {
#Autowired
private SysDatastoreRepository datastoreRepository;
#Autowired
private DataSource dataSource;
#Override
#Transactional
public Optional<SysDatastore> addStaticDatastore(String name, String tableName, String ident, Long ord) {
String createTableSql = PostgresTableSqlBuilder.createTableInPublicSchemaWithBigintPkAndFkId(
tableName,
SysObject.TABLE_NAME,
Optional.of(SysObject.ID_COLUMN_NAME)).buildSql();
Optional<SysDatastore> sysDatastore = Optional.empty();
try(
Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()
) {
connection.setAutoCommit(false);
Savepoint beforeTableCreation = connection.setSavepoint();
try {
statement.execute(createTableSql);
sysDatastore = Optional.ofNullable(
datastoreRepository.save(new SysDatastore(name, tableName, DatastoreType.STATIC, ident, ord)));
} catch(SQLException e) {
e.printStackTrace();
}
if(!sysDatastore.isPresent()) {
connection.rollback(beforeTableCreation);
} else {
connection.commit();
}
} catch(SQLException e1) {
e1.printStackTrace();
}
return sysDatastore;
}
}
So, as you can see, I receive new connection from DataSource and try to create new table. In success, I will create a new entry in the SysDataStoreRepository, and, if this fails, I will perform a rollback for the table creation.
There are some disadvantages of current approach, one of them is that table creation and entry insertion operates on separate connections (am I right?).
But I have some problem while writing a unit test. This is what I tried:
#Transactional(propagation = Propagation.REQUIRED)
#RunWith(SpringJUnit4ClassRunner.class)
#TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
#ContextConfiguration(locations = "file:src/main/webapp/WEB-INF/rest-servlet.xml")
public class SysDatastoreServiceTest {
#Autowired
private SysDatastoreService sysDatastoreService;
#Autowired
private DataSource dataSource;
#Test
public void testAddStaticDatastore() throws Exception {
Optional<SysDatastore> sysDatastore =
sysDatastoreService.addStaticDatastore("New static datastore", "new_datastore_table",
"NEW_DATASTORE_TABLE", 42L);
assertTrue(sysDatastore.isPresent());
assertEquals("New static datastore", sysDatastore.get().getName());
assertEquals("NEW_DATASTORE_TABLE", sysDatastore.get().getIdent());
assertEquals("new_datastore_table", sysDatastore.get().getTableName());
assertEquals(DatastoreType.STATIC, sysDatastore.get().getDynType());
assertEquals(42L, sysDatastore.get().getOrd().longValue());
assertTrue(dataSource.getConnection()
.getMetaData()
.getTables(null, null, sysDatastore.get().getTableName(), null)
.next());
}
This test seems pretty easy: I just compare all the fields, and then checks database for a new table.
However, this test fails when I run it twice or more times. Looking at the database I noticed, that the table new_datastore_table still remained in the schema. I guess, it was not rollbacked properly because of hand-written transaction and raw sql execution, but I am not sure.
Question: How should I write a test case for this method in a proper way? And, in case if the current approach is fundamentally wrong, how it should be changed?
Side notes: I use PostgreSQL database, and it cannot be replaced with non-relational database.
First a CREATE TABLE is a DDL statement, not a DML one. That means that a rollback will not delete the table. If you want to clean your database, you must explicitely remove it in a #After or #AfterClass method.
But do you really need to do the tests on the PostgreSQL database? Spring has great support for in memory databases and the default embedded database is HSQL which has a pretty good support for postgresql syntax. Provided you have no complex statement, it could be enough and avoids cluttering the main database for (potentially destructive) unit tests.
You could create the database in a #BeforeClass method. Here is an oversimplified example:
private static DriverManagerDataSource dataSource;
#BeforeClass
public static void setupClass() throws Exception {
ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
populator.addScript(new ClassPathResource("path/to/package/defaults.sql"));
dataSource = new DriverManagerDataSource();
dataSource.setUrl("jdbc:hsqldb:mem:pgtest;sql.syntax_pgs=true");
dataSource.setUsername("SA");
Connection con = dataSource.getConnection();
assertNotNull(con);
populator.populate(con);
con.close();
}
In my unit tests I autowired some DataSources, which use URLs like
jdbc:derby:memory:mydb;create=true
to create an in-memory DBs.
To drop an in-memory Derby db you have to connect with:
jdbc:derby:memory:mydb;drop=true
I would like this to happen after every test and start with a fresh db. How can I do this using Spring?
How to shutdown Derby in-memory database Properly
gave me a hint to a solution:
mydb.drop.url = jdbc:derby:memory:mydb;drop=true
...
<bean id="mydbDropUrl" class="java.lang.String">
<constructor-arg value="${mydb.drop.url}" />
</bean>
...
#Resource
private String mydbDropUrl;
#After
public void tearDown() {
try {
DriverManager.getConnection(mydbDropUrl);
} catch (SQLException e) {
// ignore
}
}
A downside is the use of the String constructor which accepts a String (an immutable String object around an immutable String object). I read that there is a #Value annotation in Spring 3, which might help here, but I'm using Spring 2.5.
Please let me know if you have a nicer solution.
There is a database-agnostic way to do this if you are using Spring together with Hibernate.
Make sure the application context will be created / destroyed before / after every test method:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration({"classpath*:application-context-test.xml"})
#TestExecutionListeners({DirtiesContextTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class})
#DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
public abstract class AbstractTest {
}
Instruct Hibernate to auto create the schema on startup and to drop the schema on shutdown:
hibernate.hbm2ddl.auto = create-drop
Now before every test
the application context is created and the required spring beans are injected (spring)
the database structures are created (hibernate)
the import.sql is executed if present (hibernate)
and after every test
the application context is destroyed (spring)
the database schema is dropped (hibernate).
If you are using transactions, you may want to add the TransactionalTestExecutionListener.
After spring test 3, you can use annotations to inject configurations:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration("/spring-test.xml")
public class MyTest {
}
Just do something like:
public class DatabaseTest implements ApplicationContextAware {
private ApplicationContext context;
private DataSource source;
public void setApplicationContext(ApplicationContext applicationContext) {
this.context = applicationContext;
}
#Before
public void before() {
source = (DataSource) dataSource.getBean("dataSource", DataSource.class);
}
#After
public void after() {
source = null;
}
}
Make your bean have a scope of prototype (scope="prototype"). This will get a new instance of the data source before every test.
If you use the spring-test.jar library, you can do something like this:
public class MyDataSourceSpringTest extends
AbstractTransactionalDataSourceSpringContextTests {
#Override
protected String[] getConfigLocations() {
return new String[]{"classpath:test-context.xml"};
}
#Override
protected void onSetUpInTransaction() throws Exception {
super.deleteFromTables(new String[]{"myTable"});
super.executeSqlScript("file:db/load_data.sql", true);
}
}
And an updated version based on latest comment, that drops db and recreates tables before every test:
public class MyDataSourceSpringTest extends
AbstractTransactionalDataSourceSpringContextTests {
#Override
protected String[] getConfigLocations() {
return new String[]{"classpath:test-context.xml"};
}
#Override
protected void onSetUpInTransaction() throws Exception {
super.executeSqlScript("file:db/recreate_tables.sql", true);
}
}
This is what we do at the start of every test.
Drop all Previous Objects.
Create all tables mentioned in the create_table.sql
Insert values onto the created tables based on what you want to test.
#Before
public void initialInMemoryDatabase() throws IOException, FileNotFoundException {
inMemoryDerbyDatabase.dropAllObjects();
inMemoryDerbyDatabase.executeSqlFile("/create_table_policy_version_manager.sql");
inMemoryDerbyDatabase.executeSqlFile("/insert_table_policy_version_manager.sql");
}
Works like a charm!