Last active
December 19, 2022 00:44
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 { | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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