使用@DirtiesContext

时间:2017-03-16 17:27:10

标签: java spring jpa spring-boot

我有一个Spring Boot应用程序,它使用JMS连接到队列并侦听传入的消息。在应用程序中,我有一个集成测试,它将一些消息发送到队列,然后确保当侦听器实际发生新消息时应该发生的事情。

我用@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD) 注释了我的测试类,以确保每次测试后我的数据库都是干净的。每个测试在隔离运行时都会通过。但是,当第一次测试成功通过后,当它们全部一起运行时,下一个测试将失败,下面的例外当被测代码试图将实体保存到数据库时:

    org.springframework.transaction.CannotCreateTransactionException: Could not open JPA EntityManager for transaction; nested exception is java.lang.IllegalStateException: EntityManagerFactory is closed
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:431) ~[spring-orm-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.getTransaction(AbstractPlatformTransactionManager.java:373) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.createTransactionIfNecessary(TransactionAspectSupport.java:447) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:277) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:96) ~[spring-tx-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179) ~[spring-aop-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:213) ~[spring-aop-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at com.sun.proxy.$Proxy95.handleWorkflowEvent(Unknown Source) ~[na:na]
    at com.mottmac.processflow.infra.jms.EventListener.onWorkflowEvent(EventListener.java:51) ~[classes/:na]
    at com.mottmac.processflow.infra.jms.EventListener.onMessage(EventListener.java:61) ~[classes/:na]
    at org.apache.activemq.ActiveMQMessageConsumer.dispatch(ActiveMQMessageConsumer.java:1401) [activemq-client-5.14.3.jar:5.14.3]
    at org.apache.activemq.ActiveMQSessionExecutor.dispatch(ActiveMQSessionExecutor.java:131) [activemq-client-5.14.3.jar:5.14.3]
    at org.apache.activemq.ActiveMQSessionExecutor.iterate(ActiveMQSessionExecutor.java:202) [activemq-client-5.14.3.jar:5.14.3]
    at org.apache.activemq.thread.PooledTaskRunner.runTask(PooledTaskRunner.java:133) [activemq-client-5.14.3.jar:5.14.3]
    at org.apache.activemq.thread.PooledTaskRunner$1.run(PooledTaskRunner.java:48) [activemq-client-5.14.3.jar:5.14.3]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) [na:1.8.0_77]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) [na:1.8.0_77]
    at java.lang.Thread.run(Unknown Source) [na:1.8.0_77]
Caused by: java.lang.IllegalStateException: EntityManagerFactory is closed
    at org.hibernate.jpa.internal.EntityManagerFactoryImpl.validateNotClosed(EntityManagerFactoryImpl.java:367) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.jpa.internal.EntityManagerFactoryImpl.internalCreateEntityManager(EntityManagerFactoryImpl.java:316) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.hibernate.jpa.internal.EntityManagerFactoryImpl.createEntityManager(EntityManagerFactoryImpl.java:286) ~[hibernate-entitymanager-5.0.11.Final.jar:5.0.11.Final]
    at org.springframework.orm.jpa.JpaTransactionManager.createEntityManagerForTransaction(JpaTransactionManager.java:449) ~[spring-orm-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    at org.springframework.orm.jpa.JpaTransactionManager.doBegin(JpaTransactionManager.java:369) ~[spring-orm-4.3.6.RELEASE.jar:4.3.6.RELEASE]
    ... 17 common frames omitted

我的测试班:

    @RunWith(SpringRunner.class)
@SpringBootTest(classes = { TestGovernance.class })
@DirtiesContext(classMode=ClassMode.AFTER_EACH_TEST_METHOD)
public class ActivitiIntegrationTest
{
    private static final String TEST_PROCESS_KEY = "oneTaskProcess";
    private static final String FIRST_TASK_KEY = "theTask";
    private static final String NEXT_TASK_KEY = "nextTask";

    @Autowired
    private JmsTemplate jms;

    @Autowired
    private WorkflowEventRepository eventRepository;

    @Autowired
    private TaskService taskService;

    @Test
    public void workFlowEventForRunningTaskMovesItToTheNextStage() throws InterruptedException
    {
        sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY);

        Task activeTask = getActiveTask();        
        assertThat(activeTask.getTaskDefinitionKey(), is(FIRST_TASK_KEY));

        sendMessageToUpdateExistingTask(activeTask.getProcessInstanceId(), FIRST_TASK_KEY);

        Task nextTask = getActiveTask();        
        assertThat(nextTask.getTaskDefinitionKey(), is(NEXT_TASK_KEY));
    }

    @Test
    public void newWorkflowEventIsSavedToDatabaseAndKicksOffTask() throws InterruptedException
    {
        sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY);

        assertThat(eventRepository.findAll(), hasSize(1));
    }

    @Test
    public void newWorkflowEventKicksOffTask() throws InterruptedException
    {
        sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY);

        Task activeTask = getActiveTask();        
        assertThat(activeTask.getTaskDefinitionKey(), is(FIRST_TASK_KEY));
    }


    private void sendMessageToUpdateExistingTask(String processId, String event) throws InterruptedException
    {
        WorkflowEvent message = new WorkflowEvent();
        message.setRaisedDt(ZonedDateTime.now());
        message.setEvent(event);
        // Existing
        message.setIdWorkflowInstance(processId);
        jms.convertAndSend("workflow", message);
        Thread.sleep(5000);
    }

    private void sendMessageToCreateNewInstanceOfProcess(String event) throws InterruptedException
    {
        WorkflowEvent message = new WorkflowEvent();
        message.setRaisedDt(ZonedDateTime.now());
        message.setEvent(event);
        jms.convertAndSend("workflow", message);
        Thread.sleep(5000);
    }

    private Task getActiveTask()
    {
        // For some reason the tasks in the task service are hanging around even
        // though the context is being reloaded. This means we have to get the
        // ID of the only task in the database (since it has been cleaned
        // properly) and use it to look up the task.
        WorkflowEvent workflowEvent = eventRepository.findAll().get(0);
        Task activeTask = taskService.createTaskQuery().processInstanceId(workflowEvent.getIdWorkflowInstance().toString()).singleResult();
        return activeTask;
    }

}

在应用程序中抛出异常的方法(repository只是标准的Spring Data CrudRepository):

    @Override
    @Transactional
    public void handleWorkflowEvent(WorkflowEvent event)
    {
        try
        {
            logger.info("Handling workflow event[{}]", event);

            // Exception is thrown here:
            repository.save(event);

            logger.info("Saved event to the database [{}]", event);
            if(event.getIdWorkflowInstance() == null)
            {
                String newWorkflow = engine.newWorkflow(event.getEvent(), event.getVariables());
                event.setIdWorkflowInstance(newWorkflow);
            }
            else 
            {
                engine.moveToNextStage(event.getIdWorkflowInstance(), event.getEvent(), event.getVariables());
            }
        }
        catch (Exception e)
        {
            logger.error("Error while handling workflow event:" , e);
        }
    }

我的测试配置类:

@SpringBootApplication
@EnableJms
@TestConfiguration
public class TestGovernance
{
    private static final String WORKFLOW_QUEUE_NAME = "workflow";

    @Bean
    public ConnectionFactory connectionFactory()
    {
        ConnectionFactory connectionFactory = new ActiveMQConnectionFactory("vm://localhost?broker.persistent=false");
        return connectionFactory;
    }

    @Bean
    public EventListenerJmsConnection connection(ConnectionFactory connectionFactory) throws NamingException, JMSException
    {
        // Look up ConnectionFactory and Queue
        Destination destination = new ActiveMQQueue(WORKFLOW_QUEUE_NAME);

        // Create Connection
        Connection connection = connectionFactory.createConnection();

        Session listenerSession = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
        MessageConsumer receiver = listenerSession.createConsumer(destination);

        EventListenerJmsConnection eventListenerConfig = new EventListenerJmsConnection(receiver, connection);
        return eventListenerConfig;
    }
}

JMS消息监听器(不确定这是否有帮助):

/**
 * Provides an endpoint which will listen for new JMS messages carrying
 * {@link WorkflowEvent} objects.
 */
@Service
public class EventListener implements MessageListener
{
    Logger logger = LoggerFactory.getLogger(EventListener.class);

    private WorkflowEventHandler eventHandler;

    private MessageConverter messageConverter;

    private EventListenerJmsConnection listenerConnection;

    @Autowired
    public EventListener(EventListenerJmsConnection listenerConnection, WorkflowEventHandler eventHandler, MessageConverter messageConverter)
    {
        this.eventHandler = eventHandler;
        this.messageConverter = messageConverter;
        this.listenerConnection = listenerConnection;
    }

    @PostConstruct
    public void setUpConnection() throws NamingException, JMSException
    {
        listenerConnection.setMessageListener(this);
        listenerConnection.start();
    }

    private void onWorkflowEvent(WorkflowEvent event)
    {
        logger.info("Recieved new workflow event [{}]", event);
        eventHandler.handleWorkflowEvent(event);
    }

    @Override
    public void onMessage(Message message)
    {
        try
        {
            message.acknowledge();
            WorkflowEvent fromMessage = (WorkflowEvent) messageConverter.fromMessage(message);
            onWorkflowEvent((WorkflowEvent) fromMessage);
        }
        catch (Exception e)
        {
            logger.error("Error: ", e);
        }
    }
}

我尝试添加@Transactional' to the test methods and removing it from the code under test and various combinations with no success. I've also tried adding various test execution listeners and I still can't get it to work. If I remove the @ DirtiesContext`然后异常消失,所有测试都会毫无例外地运行(但它们会因为我预期的那样失败并出现断言错误。)

非常感谢任何帮助。到目前为止,我的搜索结果还没有出现,一切都表明@DirtiesContext应该有用。

1 个答案:

答案 0 :(得分:0)

使用@DirtiesContext这是一个可怕的想法(imho)你应该做的是进行测试@Transactional。我还建议删除Thread.sleep并使用类似awaitility的内容。

理论上,当您执行查询时,应提交所有挂起的更改,以便您可以使用awaitility检查最多6秒,以查看数据库中是否存在某些内容。如果这不起作用,您可以尝试在查询之前添加刷新。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = { TestGovernance.class })
@Transactional
public class ActivitiIntegrationTest {

    private static final String TEST_PROCESS_KEY = "oneTaskProcess";
    private static final String FIRST_TASK_KEY = "theTask";
    private static final String NEXT_TASK_KEY = "nextTask";

    @Autowired
    private JmsTemplate jms;

    @Autowired
    private WorkflowEventRepository eventRepository;

    @Autowired
    private TaskService taskService;

    @Autowired
    private EntityManager em;

    @Test
    public void workFlowEventForRunningTaskMovesItToTheNextStage() throws InterruptedException
    {
        sendMessageToCreateNewInstanceOfProcess(TEST_PROCESS_KEY);

        await().atMost(6, SECONDS).until(getActiveTask() != null);

        Task activeTask = getActiveTask());
        assertThat(activeTask.getTaskDefinitionKey(), is(FIRST_TASK_KEY));

        sendMessageToUpdateExistingTask(activeTask.getProcessInstanceId(), FIRST_TASK_KEY);

        Task nextTask = getActiveTask();        
        assertThat(nextTask.getTaskDefinitionKey(), is(NEXT_TASK_KEY));
    }

    private Task getActiveTask()
    {
        em.flush(); // simulate a commit
        // For some reason the tasks in the task service are hanging around even
        // though the context is being reloaded. This means we have to get the
        // ID of the only task in the database (since it has been cleaned
        // properly) and use it to look up the task.
        WorkflowEvent workflowEvent = eventRepository.findAll().get(0);
        Task activeTask = taskService.createTaskQuery().processInstanceId(workflowEvent.getIdWorkflowInstance().toString()).singleResult();
        return activeTask;
    }

}

您可能需要/想要稍微改善一下getActiveTask return null,或者这种改变可能会使其表现得像您预期的那样。

我只是做了一个你自己可能弄明白的方法。使用这种方法获得的收益可能是2倍,1它不会再等待5秒但更少,而且您不必在测试之间重新加载整个应用程序。两者都应该使您的测试更快。