In the previous article “Database change management with Liquibase” i demonstrated the standard Liquibase usage for managing database changes.
This post will describe, how to construct an infstructure for executing database-driven unit tests, a more likely untypical task for Liquibase.
To be able to execute database-driven tests we have to put our database into a known state between test runs. This is where Liquibase will help us.
First, let’s create a sample set of changes that populates the database with the test data:
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-2.0.xsd"> <changeSet id="testdata" author="bar"> <loadData tableName="t_role" file="db/testdata/roles.csv"> <column name="name" type="STRING" /> <column name="description" type="STRING" /> </loadData> <loadData tableName="t_user" file="db/testdata/users.csv"> <column name="id" type="NUMERIC" /> <column name="username" type="STRING" /> <column name="role" type="STRING" /> </loadData> <loadData tableName="t_address" file="db/testdata/addresses.csv" /> <rollback> <delete tableName="t_address" /> <delete tableName="t_user" /> <delete tableName="t_role" /> </rollback> </changeSet> </databaseChangeLog>
In the changelog above we make use of the <loadData> tag that is able to load data from the CSV file and insert it into the database (alternatively you may use <insert>, <update> and <delete> tags to manipulate the database contents). Furthermore the <rollback> block describes how to remove the inserted changes from the database.
As brief overview, here is an example of the roles.csv file:
name,description USER,A simple user ADMIN,Administrator user ANONYMOUS,NULL
First row in the CSV file specifies the column names to populate. All subsequent rows contains the test data.
Please consult GitHub for other CSV files used in this post: https://github.com/bigpuritz/javaforge-blog/tree/master/liquibase-sample/db/testdata
Now, let’s make our project ready to use Liquibase together with the Junit.
Add following test-scoped dependencies to the pom.xml:
<dependencies> <dependency> <!-- only necessary since we're using H2 as our demonstration database --> <groupId>com.h2database</groupId> <artifactId>h2</artifactId> <version>1.3.168</version> <scope>test</scope> </dependency> <dependency> <groupId>org.liquibase</groupId> <artifactId>liquibase-core</artifactId> <version>2.0.5</version> <scope>test</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.10</version> <scope>test</scope> </dependency> </dependencies>
In the next step we’ll create a skeleton for our testcase, that will put the test data into the database only once on startup and remove it on teardown after all tests are executed.
public class TestWithLiquibase { private static Connection conn; private static Liquibase liquibase; @BeforeClass public static void createTestData() throws SQLException, ClassNotFoundException, LiquibaseException { Class.forName("org.h2.Driver"); conn = DriverManager.getConnection("jdbc:h2:liquibase-sample", "sa", "sa"); Database database = DatabaseFactory.getInstance() .findCorrectDatabaseImplementation(new JdbcConnection(conn)); liquibase = new Liquibase("db/testdata/db.testdata.xml", new FileSystemResourceAccessor(), database); liquibase.update(null); } @AfterClass public static void removeTestData() throws LiquibaseException, SQLException { liquibase.rollback(1000, null); conn.close(); } }
Now, writing a database-driven unit tests is quite simple. Following few tests just giving an idea, how to do this:
@Test public void testUsers() throws SQLException { PreparedStatement stmt = null; ResultSet rs = null; try { stmt = conn.prepareStatement("select count(*) as numberOfUsers from t_user"); rs = stmt.executeQuery(); rs.next(); int numberOfUsers = rs.getInt("numberOfUsers"); Assert.assertEquals(3, numberOfUsers); } finally { rs.close(); stmt.close(); } } @Test public void testRoles() throws SQLException { PreparedStatement stmt = null; ResultSet rs = null; try { stmt = conn.prepareStatement("select count(*) as numberOfRoles from t_role"); rs = stmt.executeQuery(); rs.next(); int numberOfUsers = rs.getInt("numberOfRoles"); Assert.assertEquals(3, numberOfUsers); } finally { rs.close(); stmt.close(); } }
Done.
Consult the GitHub repository for the sample project sources.