Skip to content

Instantly share code, notes, and snippets.

@appkr
Last active October 23, 2020 07:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save appkr/3298b10b2de55848e1c7e845229873f8 to your computer and use it in GitHub Desktop.
Save appkr/3298b10b2de55848e1c7e845229873f8 to your computer and use it in GitHub Desktop.
snippets for Integration Test (Test Isolation)
package ...;
import org.springframework.stereotype.Component;
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;
@Component
public class DatabaseCleaner {
private final DataSource dataSource;
public DatabaseCleaner(DataSource dataSource) {
this.dataSource = dataSource;
}
public void execute() {
try (Connection c = dataSource.getConnection();
Statement s = c.createStatement()) {
// Disable FK constraint
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 constraint
s.execute("SET REFERENTIAL_INTEGRITY TRUE");
} catch (SQLException e) {
e.printStackTrace();
}
}
}
// Alternative approach: use @Sql("path.sql") on a test class
package ...;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import javax.persistence.EntityManager;
@Component
public class FinancialInstituteSeeder {
private final EntityManager em;
public FinancialInstituteSeeder(EntityManager em) {
this.em = em;
}
@Transactional
public void execute() {
// em.flush();
em.createNativeQuery("TRUNCATE TABLE table_name").executeUpdate();
final String query = "INSERT INTO table_name (...) VALUES (...);
em.createNativeQuery(query).executeUpdate();
}
}
package ...;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
public class IntegrationTestExecutionListener extends AbstractTestExecutionListener {
@Autowired
private DatabaseCleaner databaseCleaner;
private boolean cleaned = false;
@Override
public final int getOrder() {
return 2001;
}
@Override
public void beforeTestClass(TestContext testContext) {
testContext.getApplicationContext()
.getAutowireCapableBeanFactory()
.autowireBean(this);
}
@Override
public void prepareTestInstance(TestContext testContext) throws Exception {
if (!cleaned) {
databaseCleaner.execute();
cleaned = true;
}
}
@Override
public void afterTestClass(TestContext testContext) throws Exception {
databaseCleaner.execute();
}
}
package ...;
import liquibase.Liquibase;
import liquibase.integration.spring.SpringLiquibase;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.sql.DataSource;
import java.sql.Connection;
import java.util.*;
@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = Application.class)
@ActiveProfiles(Constants.Profile.TEST_PROFILE)
@Tag("integration")
@TestExecutionListeners(mergeMode =
TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
listeners = {IntegrationTestExecutionListener.class}
)
public abstract class IntegrationTestSupport {
@Autowired
private SpringLiquibase springLiquibase;
@Autowired
private DataSource dataSource;
@BeforeEach
@SuppressWarnings("rawtypes")
public void setup() throws Exception {
// NOTE. Be careful with the Connection! It may lead to a connection leak situation, if we don't close it.
// @see https://mkyong.com/jdbc/hikaripool-1-connection-is-not-available-request-timed-out-after-30002ms/
// @see https://do-study.tistory.com/97
try (Connection connection = dataSource.getConnection()) {
Map<Class, Object> args = new HashMap<>();
args.put(Connection.class, connection);
// Unfortunately SpringLiquibase#createLiquibase is a protected method,
// meaning no way for us to invoke it, without a reflection magic
Liquibase liquibase =
(Liquibase) TestUtil.invokeProtectedMethod(springLiquibase, "createLiquibase", args);
// Reference "For programmatic use of Liquibase in Spring project"
// @see "Executing Liquibase: 3 Use Cases" \https://www.liquibase.org/blog/executing-liquibase
// Commented out for purpose
// springLiquibase.setDropFirst(true);
liquibase.update("test");
}
}
}
@appkr
Copy link
Author

appkr commented Oct 23, 2020

Another way

import org.springframework.context.ApplicationContext;
import org.springframework.test.context.TestContext;
import org.springframework.test.context.support.AbstractTestExecutionListener;
import org.springframework.transaction.support.TransactionTemplate;

import javax.persistence.EntityManager;

public class DataInitializeExecutionListener extends AbstractTestExecutionListener {
    @Override
    public void afterTestMethod(TestContext testContext) {
        ApplicationContext applicationContext = testContext.getApplicationContext();
        TransactionTemplate transactionTemplate = applicationContext.getBean(TransactionTemplate.class);
        transactionTemplate.execute(status -> {
            EntityManager em = applicationContext.getBean(EntityManager.class);
            em.createQuery("SELECT i FROM table_name i").getResultStream().forEach(em::remove);
            em.flush();
            return null;
        });
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment