postgresqldbunitspring-test-dbunit

DBUnit & Postgres UUID Primary Key


I am trying to use DBUnit to run integration tests, however I am finding myself unable to insert the primary key columns, which obviously will not work with foreign keys referencing the primary key later on in the file.

For example, I have the following DDL:

CREATE TABLE attributes(
    attribute_id UUID NOT NULL DEFAULT uuid_generate_v4(),
    attribute VARCHAR(64) NOT NULL,
    description TEXT NOT NULL,
    PRIMARY KEY(attribute_id)
);

And the DBUnit setup XML looks like:

<?xml version="1.0" encoding="UTF-8" ?>
<dataset>
    <attributes attribute_id="233bc966-4fcd-4b46-88e6-3e07090f322d" attribute="Empathy" description="Empathy Description" />
</dataset>

When I attempt to run the test, I get the failure:

    org.dbunit.dataset.NoSuchColumnException: attributes.ATTRIBUTE_ID -  (Non-uppercase input column: attribute_id) in ColumnNameToIndexes cache map. Note that the
ap's column names are NOT case sensitive.

Here is the test being run:

@RunWith(SpringJUnit4ClassRunner.class)
@WebAppConfiguration
@DbUnitConfiguration(dataSetLoader = TestConfiguration.FlatXmlDataLoaderProxy.class)
@ContextConfiguration(classes = {ApplicationConfiguration.class, TestConfiguration.class})
@TestExecutionListeners({
        DependencyInjectionTestExecutionListener.class,
        DirtiesContextTestExecutionListener.class,
        TransactionalTestExecutionListener.class,
        DbUnitTestExecutionListener.class
})
public class ApplicationAssessmentJobTest {
    @Autowired
    private ApplicationAssessmentJob applicationAssessmentJob;

    @Test
    @DatabaseSetup("/dbunit/ApplicationAssessmentJobTestSetup.xml")
    @DatabaseTearDown("dbunit/ApplicationAssessmentJobTestTearDown.xml")
    public void testJob() {
        ApplicationAssessmentJobModel model = new ApplicationAssessmentJobModel();
        model.setApplicationId(UUID.fromString("41fa1d51-c1ee-482b-80a7-a6eefda64436"));

        applicationAssessmentJob.receiveMessage(model);
    }
}

This error shown does not appear to be directly related to the underlying issue. If I remove the attribute_id column from the XML, the record is inserted.


Solution

  • After fighting with DBUnit/Spring-Test-DBUnit for most of my day, I decided to scrap that library and roll my own for the mere fact that DBUnit seems to be nothing but pain.

    I was able to write a setup/teardown in about 40 SLoC in less than 30 minutes. It uses plain SQL, which is philosophically more in line with my choice of jOOQ over Hibernate. Certainly less than ideal, but 15 minutes of searching didn't turn up anything to handle a simple use case of running SQL on setup and on teardown. Worth noting is a requirement on a java.sql.DataSource Bean available from the ApplicationContext.

    DbInitTestExecutionListener.java

    import org.springframework.core.io.ClassPathResource;
    import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
    import org.springframework.test.context.TestContext;
    import org.springframework.test.context.TestExecutionListener;
    
    import javax.sql.DataSource;
    import java.util.Arrays;
    
    public class DbInitTestExecutionListener implements TestExecutionListener {
        @Override
        public void beforeTestClass(TestContext testContext) throws Exception {}
    
        @Override
        public void prepareTestInstance(TestContext testContext) throws Exception {}
    
        @Override
        public void beforeTestMethod(TestContext testContext) throws Exception {
            DatabaseSetup setup = testContext.getTestMethod().getAnnotation(DatabaseSetup.class);
    
            if (setup != null) {
                if (setup.clearInsert()) {
                    afterTestMethod(testContext);
                }
    
                ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    
                Arrays.asList(setup.value()).stream()
                        .map(ClassPathResource::new)
                        .forEach(populator::addScript);
    
                populator.execute(testContext.getApplicationContext().getBean(DataSource.class));
            }
        }
    
        @Override
        public void afterTestMethod(TestContext testContext) throws Exception {
            DatabaseTearDown tearDown = testContext.getTestMethod().getAnnotation(DatabaseTearDown.class);
    
            if (tearDown != null) {
                ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    
                Arrays.asList(tearDown.value()).stream()
                        .map(ClassPathResource::new)
                        .forEach(populator::addScript);
    
                populator.execute(testContext.getApplicationContext().getBean(DataSource.class));
            }
        }
    
        @Override
        public void afterTestClass(TestContext testContext) throws Exception {}
    }
    

    DatabaseTearDown.java

    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    public @interface DatabaseTearDown {
        String[] value();
    }
    

    DatabaseSetup.java

    import java.lang.annotation.*;
    
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ ElementType.METHOD })
    public @interface DatabaseSetup {
        boolean clearInsert() default true;
    
        String[] value();
    }
    

    With a minimal test configuration:

    @RunWith(SpringJUnit4ClassRunner.class)
    @WebAppConfiguration
    @ContextConfiguration(classes = {ApplicationConfiguration.class})
    @TestExecutionListeners({
            DependencyInjectionTestExecutionListener.class,
            DirtiesContextTestExecutionListener.class,
            TransactionalTestExecutionListener.class,
            DbInitTestExecutionListener.class
    })
    public class TestClass {
        @Test
        @DatabaseSetup("/dbinit/TestSetup.sql")
        @DatabaseTearDown("/dbinit/TestTearDown.sql")
        public void testJob() {
          // TODO: Add test code here
        }
    }