使用 Spring Boot 在 Activiti 中为 JUnit 测试构建 ApplicationContext 时出现异常

Exception when building the ApplicationContext for a JUnit test in Activiti with Spring Boot

每当我尝试 运行 使用 Spring Boot 对 Activiti 进行以下 JUnit 测试时,我都无法解决以下异常。

异常:

2015-05-07 20:15:00 org.springframework.test.context.TestContext.<init>(93@main) INFO @ContextConfiguration not found for class [class ProcessXTest].
2015-05-07 20:15:00 org.springframework.test.context.TestContextManager.retrieveTestExecutionListeners(143@main) INFO @TestExecutionListeners is not present for class [class ProcessXTest]: using defaults.
2015-05-07 20:15:00 org.springframework.test.context.TestContextManager.prepareTestInstance(234@main) ERROR Caught exception while allowing TestExecutionListener [org.springframework.test.context.support.DependencyInjectionTestExecutionListener@232023ff] to prepare test instance [ProcessXTest@131b4c5d]
java.lang.IllegalArgumentException: Can not build an ApplicationContext with a NULL 'contextLoader'. Consider annotating your test class with @ContextConfiguration.

JUnit 测试:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class })
public class ProcessXTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(ProcessXTest.class);

    private static final String BPMN_LOCATION = "./src/main/resources/processes/processx.bpmn";
    private static final String BPMN_NAME = "processx.bpmn";
    private static final String PROCESS_KEY = "processx";

    @Rule
    public ActivitiRule activitiRule = new ActivitiRule();

    @Before
    public void loadProces() throws FileNotFoundException {
        LOGGER.debug("Loading the model for unit testing.");

        RepositoryService repositoryService = activitiRule.getRepositoryService();
        Deployment deployment = repositoryService.createDeployment().addInputStream(BPMN_NAME, new FileInputStream(BPMN_LOCATION)).deploy();
        assertNotNull(deployment.getId());

        assertTrue(repositoryService.getDeploymentResourceNames(deployment.getId()).contains(bpmnName));
    }

    @Test
    public void testProces() {
        LOGGER.debug("Unit testing the process.");

        TaskService taskService = activitiRule.getTaskService();
        List<Task> tasks = taskService.createTaskQuery().list();
        assertEquals(tasks.size(), 0);

        ProcessInstance processInstance = activitiRule.getRuntimeService().startProcessInstanceByKey(PROCESS_KEY, new HashMap<String, Object>()); 
        assertNotNull(processInstance.getId());

        tasks = taskService.createTaskQuery().list();
        assertEquals(tasks.size(), 1);
    }
}

主应用程序 class(所有 bean 都在此 class 中定义,因此没有使用外部 XML 上下文配置文件):

@SpringBootApplication
@EnableConfigurationProperties
public class Application {

    public static void main(final String[] args) {
       SpringApplication.run(Application.class, args);
    }

    @Bean
    public CommandLineRunner init() {
        return new CommandLineRunner() {
            @Override
            public void run(final String... strings) throws Exception {
                // initialization on start-up
            }
        };
    }

    ...more beans
}

我正在使用以下依赖项:

compile 'org.activiti:activiti-engine:5.17.0'
compile 'org.activiti:spring-boot-starter-basic:5.17.0'
compile 'org.activiti:spring-boot-starter-rest-api:5.17.0'

compile 'junit:junit:4.12'

compile 'org.springframework.boot:spring-boot-starter-web:1.2.3.RELEASE'
compile 'org.springframework.boot:spring-boot-starter-test:1.2.3.RELEASE'

我终于通过如下定义 JUnit 测试解决了这个问题:

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = { Application.class })
public class ProcessXTest {

    private static final Logger LOGGER = LoggerFactory.getLogger(ProcessXTest.class);

    private static final String BPMN_LOCATION = "./src/main/resources/processes/processx.bpmn";
    private static final String BPMN_NAME = "processx.bpmn";
    private static final String PROCESS_KEY = "processx";

    @Autowired
    private RepositoryService repositoryService;

    @Autowired
    private RuntimeService runtimeService;

    @Autowired
    private TaskService taskService;

    @Autowired
    private HistoryService historyService;

    @Before
    public void loadModel() throws FileNotFoundException {
        LOGGER.debug("Loading the model for unit testing.");

        Deployment deployment = repositoryService.createDeployment().addInputStream(BPMN_NAME, new FileInputStream(BPMN_LOCATION)).deploy();
        assertNotNull(deployment.getId());
        assertTrue(repositoryService.getDeploymentResourceNames(deployment.getId()).contains(bpmnName));
    }

    @Test
    public void testProcess() {
        LOGGER.debug("Unit testing the process.");

        List<Task> tasks = taskService.createTaskQuery().list();
        assertEquals(tasks.size(), 0);

        ProcessInstance processInstance = runtimeService.startProcessInstanceByKey(PROCESS_KEY, new HashMap<String, Object>());
        assertNotNull(processInstance.getId());

        tasks = taskService.createTaskQuery().list();
        assertEquals(tasks.size(), 1);
    }
}

考虑到 @ActivitiRule 默认使用类路径上的 activiti.cfg.xml 资源初始化 ProcessEgine,我不得不修改我的 JUnit 测试:http://activiti.org/javadocs/org/activiti/engine/test/ActivitiRule.html

我的解决方案:

import org.activiti.engine.ProcessEngine;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.test.ActivitiRule;
import org.activiti.spring.boot.AbstractProcessEngineConfiguration;
import org.junit.Rule;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(AbstractSpringTest.ActivitiEngineTestConfiguration.class)
@ActiveProfiles("test")
public abstract class AbstractSpringTest {

    @Autowired @Rule
    public ActivitiRule activitiRule;

    @SpringBootApplication
    public static class ActivitiEngineTestConfiguration {

        @Bean
        public ActivitiRule activitiRule(ProcessEngine processEngine) {
            return new ActivitiRule(processEngine);
        }
    }
}

有一个文件 application-test.properties 包含(在此示例中使用 h2):

spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.url=jdbc:h2:mem:activiti?characterEncoding=UTF-8
spring.datasource.username=sa
spring.datasource.password=sa

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.show-sql=true

A​​bstractSpringTest 的每个子类都可以使用例如@Deployment:

public class ConcreteTest extends AbstractSpringTest {

    @Test
    @Deployment(resources = {"processes/test_process.bpmn20.xml"})
    public void testSomething() {
    }
}