15

I want to use a custom TestExecutionListener in combination with SpringJUnit4ClassRunner to run a Liquibase schema setup on my test database. My TestExecutionListener works fine but when I use the annotation on my class the injection of the DAO under test no longer works, at least the instance is null.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/applicationContext-test.xml" })
@TestExecutionListeners({ LiquibaseTestExecutionListener.class })
@LiquibaseChangeSet(changeSetLocations={"liquibase/v001/createTables.xml"})
public class DeviceDAOTest {

    ...

    @Inject
    DeviceDAO deviceDAO;

    @Test
    public void findByCategory_categoryHasSubCategories_returnsAllDescendantsDevices() {
        List<Device> devices = deviceDAO.findByCategory(1); // deviceDAO null -> NPE
        ...
    }
}

The listener is fairly simple:

public class LiquibaseTestExecutionListener extends AbstractTestExecutionListener {

    @Override
    public void beforeTestClass(TestContext testContext) throws Exception {
        final LiquibaseChangeSet annotation = AnnotationUtils.findAnnotation(testContext.getTestClass(),
                LiquibaseChangeSet.class);
        if (annotation != null) {
            executeChangesets(testContext, annotation.changeSetLocations());
        }
    }

    private void executeChangesets(TestContext testContext, String[] changeSetLocation) throws SQLException,
            LiquibaseException {
        for (String location : changeSetLocation) {
            DataSource datasource = testContext.getApplicationContext().getBean(DataSource.class);
            DatabaseConnection database = new JdbcConnection(datasource.getConnection());
            Liquibase liquibase = new Liquibase(location, new FileSystemResourceAccessor(), database);
            liquibase.update(null);
        }
    }

}

There are no errors in the log, just a NullPointerException in my test. I don't see how the use of my TestExecutionListener affects the autowiring or injection.

JeanValjean
  • 17,172
  • 23
  • 113
  • 157
nansen
  • 2,912
  • 1
  • 20
  • 33

2 Answers2

23

I had a look at the spring DEBUG logs and found that when I omit my own TestExecutionListener spring sets a DependencyInjectionTestExecutionListener in place. When annotating the test with @TestExecutionListeners that listener gets overwritten.

So I just added the DependencyInjectionTestExecutionListener explicitly with my custom one and everything works fine:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "file:src/main/webapp/WEB-INF/applicationContext-test.xml" })
@TestExecutionListeners(listeners = { LiquibaseTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class })
@LiquibaseChangeSet(changeSetLocations = { "liquibase/v001/createTables.xml" })
public class DeviceDAOTest {
    ...

UPDATE: The behavior is documented here.

... Alternatively, you can disable dependency injection altogether by explicitly configuring your class with @TestExecutionListeners and omitting DependencyInjectionTestExecutionListener.class from the list of listeners.

nansen
  • 2,912
  • 1
  • 20
  • 33
  • 1
    That's correct: if you specify a custom `TestExecutionListener` via `@TestExecutionListeners` you implicitly override all of the default TestExecutionListeners. Granted, this functionality is perhaps not that well documented. So feel free to open a JIRA issue to request an improvement to the documentation. ;) – Sam Brannen Mar 30 '13 at 12:42
  • 1
    @SamBrannen: Actually it is documented - perhaps somewhat implicitly. See my updated answer. – nansen Mar 30 '13 at 13:16
  • 2
    I'm well aware of the text you quoted since I wrote it. ;) But... that doesn't explicitly describe the scenario you encountered. That's why I suggested you open a JIRA ticket to improve the documentation. – Sam Brannen Jun 26 '13 at 17:03
  • 7
    Worth adding that since Spring 4.1, `mergeMode = MergeMode.MERGE_WITH_DEFAULTS` can be used to include all the default TestExecutionListeners. – Steve Chambers Sep 18 '15 at 12:01
7

I would recommend to consider just doing something like:

@TestExecutionListeners(
        mergeMode =TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS,
        listeners = {MySuperfancyListener.class}
)

so that you do not need to know which listeners are required. I recommend this approach because a struggled a few minutes with SpringBoot trying to make it working right just by using DependencyInjectionTestExecutionListener.class.

JeanValjean
  • 17,172
  • 23
  • 113
  • 157