Skip to content

Instantly share code, notes, and snippets.

@itzg
Last active December 19, 2022 00:44
Show Gist options
  • Save itzg/5270894 to your computer and use it in GitHub Desktop.
Save itzg/5270894 to your computer and use it in GitHub Desktop.
Using Sping's JUnit runner to build-up a Postgres database environment where the schema is managed by Liquibase. The tables are automatically emptied before each test method.
import javax.sql.DataSource;
import org.junit.runner.RunWith;
import org.skife.jdbi.v2.DBI;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.TestExecutionListeners;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.DependencyInjectionTestExecutionListener;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={TestDatastoreConfig.class})
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class,DbCleaningTestListener.class})
public abstract class AbstractRepositoryTest {
// Common injections
@Autowired
protected DBI dbi;
@Autowired
protected JdbcTemplate jdbcTemplate;
@Autowired
protected DataSource dataSource;
}
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.TestExecutionListener;
import org.springframework.util.StringUtils;
public class DbCleaningTestListener implements TestExecutionListener {
@Override
public void beforeTestMethod(TestContext testContext) throws Exception {
ApplicationContext appContext = testContext.getApplicationContext();
DataSource ds = appContext.getBean(DataSource.class);
try (Connection conn = ds.getConnection()) {
Statement stmt = conn.createStatement();
Object databaseName = appContext.getBean("databaseName");
ResultSet rs = stmt.executeQuery(String.format("SELECT table_name" +
" FROM information_schema.tables" +
" WHERE table_catalog = '%s' and table_schema = 'public'" +
" and table_name not like 'databasechange%%'",
databaseName));
List<String> tables = new ArrayList<>();
while (rs.next()) {
tables.add(rs.getString(1));
}
stmt.execute(String.format("TRUNCATE TABLE %s", StringUtils.collectionToCommaDelimitedString(tables)));
}
}
@Override
public void afterTestMethod(TestContext testContext) throws Exception {
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
}
@Override
public void beforeTestClass(TestContext testContext) throws Exception {
}
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
}
}
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import liquibase.exception.LiquibaseException;
import liquibase.logging.LogFactory;
import org.junit.Before;
import org.junit.Test;
import org.springframework.test.util.ReflectionTestUtils;
public class CampaignRepositoryTest extends AbstractRepositoryTest {
private CampaignRepository repo;
@Before
public void setup() throws LiquibaseException {
LogFactory.setLoggingLevel("WARNING");
repo = new CampaignRepository();
ReflectionTestUtils.setField(repo, "dbi", dbi);
ReflectionTestUtils.setField(repo, "jdbcTemplate", jdbcTemplate);
repo.init();
// snip
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import javax.annotation.PreDestroy;
import javax.sql.DataSource;
import liquibase.Liquibase;
import liquibase.database.jvm.JdbcConnection;
import liquibase.exception.LiquibaseException;
import liquibase.resource.ClassLoaderResourceAccessor;
import org.postgresql.ds.PGSimpleDataSource;
import org.skife.jdbi.v2.DBI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.env.Environment;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* We'll use Java based Spring config to allow for dynamically generating a test database name, etc.
* @author gbourne
*
*/
@Configuration
public class TestDatastoreConfig {
private static final Logger logger = LoggerFactory.getLogger(TestDatastoreConfig.class);
@Autowired
private Environment env;
// autowiring to allow the PreDestroy "access" to a singleton bean during destruction
@Autowired
private String databaseName;
@Bean
public String databaseName() {
return String.format("temp_%s_%x", env.getProperty("testDbNameQualifier", "test"),
System.currentTimeMillis());
}
@Bean
public DataSource dataSource() throws SQLException {
PGSimpleDataSource ds = createInitialDataSource();
final String dbUser = env.getProperty("testDbUser", "orwell");
try (Connection adminConnection = getAdminConnection(ds)) {
Statement stmt = adminConnection.createStatement();
// This code is executing within a Spring-fied code space, so databaseName() is actually
// dipping into the Spring context and isn't a plain old method call.
stmt.execute(String.format("CREATE DATABASE %s WITH OWNER = %s", databaseName(), dbUser));
}
ds.setDatabaseName(databaseName());
try (Connection adminConnection = getAdminConnection(ds)) {
adminConnection.createStatement().execute("CREATE EXTENSION postgis");
}
ds.setUser(dbUser);
ds.setPassword(env.getProperty("testDbPassword","orwell"));
logger.debug("Using the database {}", databaseName());
return ds;
}
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
private Connection getAdminConnection(PGSimpleDataSource ds) throws SQLException {
return ds.getConnection(
env.getProperty("testDbAdminUser", System.getProperty("user.name")),
env.getProperty("testDbAdminPassword", ""));
}
private PGSimpleDataSource createInitialDataSource() {
PGSimpleDataSource ds = new PGSimpleDataSource();
ds.setServerName(env.getProperty("testDbServerName", "localhost"));
ds.setPortNumber(Integer.parseInt(env.getProperty("testDbPortNumber", "5432")));
// temporarily point to root database
ds.setDatabaseName("");
return ds;
}
@PreDestroy
public void teardown() throws SQLException {
if (Boolean.parseBoolean(env.getProperty("testDbDeleteContent", "true"))) {
logger.debug("Dropping the database {}. Set -DtestDbDeleteContent=false if you want it to stick around next time.", databaseName);
// Can't leverage the one created within the Spring context since we need a non-database name
PGSimpleDataSource ds = createInitialDataSource();
try (Connection adminConnection = getAdminConnection(ds)) {
Statement stmt = adminConnection.createStatement();
stmt.execute(String.format("DROP DATABASE %s", databaseName));
}
}
}
@Bean(destroyMethod="close")
public Connection connection(DataSource ds) throws SQLException {
Connection connection = ds.getConnection();
return connection;
}
@Bean
public Liquibase liquibase(Connection connection) throws LiquibaseException, SQLException {
Liquibase liquibase = new Liquibase("configdb/db-changelog.xml",
new ClassLoaderResourceAccessor(),
new JdbcConnection(connection));
liquibase.update(null);
return liquibase;
}
@Bean
public DBI dbi(DataSource ds) {
return new DBI(ds);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment