2

I'm writing integration tests for a Spring Batch job. I want to use repository populators to load test data into an in-memory database before each test.

The examples I'm finding so far seem to indicate that a repository populator will only populate once when the context is initialized. This is a problem for me because each test method should have it's own test data loaded in the repositories.

Essentially, here's what I'm trying to do:

  1. Load Spring context
  2. Initialize in-memory database and create schema
  3. Before Test
    1. Load test data using repopsitory populator
  4. Run Test
  5. After Test
    1. Drop all data from in-memory database

I haven't been able to find an easy way to do this (specifically step 3.1) yet. I have some ideas but thought I'd see if anyone else has tried this too.

4

3 回答 3

3

一个有趣的挑战。听起来您想要的是 ResourceReaderRepositoryPopulator 的变体,它被 TestExecutionListener 以与https://github.com/springtestdbunit/spring-test-dbunit/对 DbUnit 文件相同的方式连接。

删除操作只是对已插入的每个对象调用 repository.delete(object) 的情况。

听起来您正在尝试做的是 spring-test-dbunit 的 Spring Data 版本,您应该能够基于该代码加上:ResourceReaderRepositoryPopulator 和 AbstractRepositoryPopulatorFactoryBean 来执行此操作。

于 2013-06-14T15:49:23.873 回答
1

在我结束的每个测试之前填充存储库的解决方案是一个由感兴趣的测试类扩展的基类。

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
public abstract class MockedApplicationContext {

  protected MockMvc mockMvc;

  @Autowired
  private WebApplicationContext webApplicationContext;

  @Autowired
  private ResourceReaderRepositoryPopulator populator;

  @Autowired
  private UserRepository userRepository;

  @Before
  public void setup() throws Exception {
    this.mockMvc = webAppContextSetup(webApplicationContext).build();
    userRepository.deleteAll();
    Repositories repositories = new Repositories(webApplicationContext);
    populator.populate(repositories);
  }
}
于 2015-08-19T08:34:36.710 回答
0

Here's an example that I was able to get working.

I utilize the @ContextConfiguration annotation to start the context using 3 different files. "MyJobConfiguration.xml" contains the production batch job definition which I won't include here (irrelevant to this question). "BatchIntegrationTests.xml" is a common context configuration used for all of my batch integration tests. It handles overriding the datasource to use an in-memory database that automatically generates the schema. Again, not included.

"BatchJobIT.xml" is where I set up the actual repository populator I will be using. Unfortunately, the repository:unmarshaller-populator tag requires the locations attribute to be specified; so I needed to provide a 'dummy' xml file (com/example/anyData.xml) that the repository populator will load when the application contexts starts. I also specify to use an XStream Unmarshaller and supply any aliases it might encounter during my tests.

BatchJobIT.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--  
 *
 * Additional Configuration for BatchJobIT integration tests.
 * 
-->


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:repository="http://www.springframework.org/schema/data/repository"
    xmlns:oxm="http://www.springframework.org/schema/oxm"
    xsi:schemaLocation="http://www.springframework.org/schema/data/repository http://www.springframework.org/schema/data/repository/spring-repository-1.5.xsd
        http://www.springframework.org/schema/oxm http://www.springframework.org/schema/oxm/spring-oxm-3.1.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd">

    <repository:unmarshaller-populator
        id="populator" locations="classpath:com/example/anyData.xml"
        unmarshaller-ref="xstreamMarshaller" />



    <bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
        <property name="aliases">
            <props>
                <prop key="domainObjects">java.util.List</prop>
                <prop key="domainObject">com.example.domain.MyObject</prop>
            </props>
        </property>
    </bean>

</beans>

The actual test class is then pretty straightforward... with a little hackiness going on in the @Setup. First, I @Autowire the populator to get a reference to it. I also need a reference to the application context, so I @Autowire that as well.

For this example, I search the classpath for a test-data.xml file qualified by the test class name and test method name. Then, I set that test-data file as the resource location on the populator and fire off a ContextRefreshedEvent which causes the populator to re-populate it's repositories.

BatchJobIT.java

package com.example;

import static org.junit.Assert.assertNotNull;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.data.repository.init.UnmarshallerRepositoryPopulatorFactoryBean;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * This integration test will test a Spring Batch job.
 */

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "/modules/MyJobConfiguration.xml", "BatchIntegrationTests.xml", "BatchJobIT.xml" })
public class BatchJobIT {

    @Autowired
    private EntityManagerFactory entityManagerFactory;

    @Autowired
    private UnmarshallerRepositoryPopulatorFactoryBean populator;

    //Keep track of the test name for use within the test methods
    @Rule
    public TestName name = new TestName();

    @Autowired
    private ApplicationContext applicationContext;

    @Before
    public void setUp() throws Exception {

        //Dynamically build the path to the test-data file. In this example, classpath will be searched for
        //'com/example/BatchJobIT.testBatchJob.test-data.xml'
        StringBuilder testDataFileName = new StringBuilder();
        testDataFileName.append(this.getClass().getCanonicalName());
        testDataFileName.replace(0, testDataFileName.length(), testDataFileName.toString().replaceAll("\\.", "/"));
        testDataFileName.append(".");
        testDataFileName.append(name.getMethodName());
        testDataFileName.append(".test-data.xml");

        //Set new resource location
        populator.getObject().setResourceLocation(testDataFileName.toString());
        //Send a context refreshed event to the populator which causes it to re-populate repositories
        populator.onApplicationEvent(new ContextRefreshedEvent(applicationContext));
    }

    @After
    public void tearDown() throws Exception {

        //Delete test data from database
        EntityManager em = entityManagerFactory.createEntityManager();
        try {
            em.getTransaction().begin();
            em.createNativeQuery("TRUNCATE SCHEMA public AND COMMIT").executeUpdate();
            if (em.getTransaction().isActive()) {
                em.getTransaction().commit();
            }
        } catch (Exception e) {
            if (em.getTransaction().isActive()) {
                em.getTransaction().rollback();
            }
            throw e;
        } finally {
            em.close();
        }
    }

    @Test
    public void testBatchJob() throws Exception {
        //Uncomment to launch hsqldb manager to inspect that data was loaded
        //org.hsqldb.util.DatabaseManagerSwing.main(new String[] { "--url", "jdbc:hsqldb:mem:testdb" });


        //Run test

        assertNotNull(populator);
    }

}

This does feel a little hacky but I think it's robust enough until I can look closer at spring-test-dbunit like @NealeU suggested.

于 2013-06-14T17:30:36.267 回答