2017-03-16 11 views
0

JMSを使用してキューに接続し、受信メッセージをリスンするSpring起動アプリケーションがあります。このアプリケーションでは、いくつかのメッセージをキューに送信する統合テストがあり、次にリスナーが新しいメッセージを実際に受け取ったときに起こるはずのことを確認します。コンテキストが@DirtiesContextでリロードされた後にEntityManagerFactoryが閉じられる

私は、テストごとに@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ちょうど標準スプリングデータ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); 
     } 
    } 
} 

私はDirtiesContext` @@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が動作するはずです。

+0

これは、dirtiesコンテキストを使用する非常に悪い理由です。それが遅いとあなたのテストスイートが成長し、豆の数がさらに遅くなることをしないでください。そうしないでください。あなたのテストを '@ Transactional'にして、デフォルトはテスト後にデータがロールバックされることです。それらはコミットされていないので失敗する可能性があるので、 'EntityManager'を注入し、コミットをシミュレートするためにメソッド呼び出しの間に' entityManager.flush() 'を置く必要があるかもしれません。 SpringBootTest(気づいたばかり)を使用していても、テスト全体のアプリケーションを再起動するのはもっと恐ろしい考えです。 –

+0

さらに、私はあなたのJMS設定に欠陥があると言うでしょうし、あなたはそれをずっと簡単にすることができます。 '@MmsLIstener'を使って' onMessage'を実装するだけで、そこにいくつかのキュー名が残っています。 –

+0

私はもともと '@ JmsListener'を持っていましたが、自動設定がそれを設定する方法については、Microsoft Service Busと組み合わされたときに本番環境では動作しませんでした。 –

答えて

0

これは@DirtiesContextを使用するとひどい考えです(imho)あなたのテストは@Transactionalです。 Thread.sleepを削除し、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秒以上待たずにテスト全体にアプリケーション全体をリロードする必要はありません。どちらもあなたのテストをより速くするはずです。

+0

私は '@ Transactional'を使うようにテストを更新しましたが、データベースを駆除しているようには見えません。 '2017-03-16 19:24:11.216 DEBUG 12348 --- [main] osorm.jpa.JpaTransactionManager:EntityManagerでJPAトランザクションをロールバックする[org.hibernate.jpa] .internal.EntityManagerImpl @ 18c820d2] 'しかし、すべてのテストを一緒に実行すると、最後のテスト方法になり、データベースには4つの項目があります。テストは分離して行われるので、データベースにすべてのテストを置くことは間違いないと私は知っています。 –

+0

私はログを見ていて、テスト中のコードが別のスレッドで実行されているということが何か関係があるのだろうかと疑問に思っています。 JMSメッセージ処理はスレッド '[Session Task-1]'上で動作し、テストはスレッド '[main]'で実行されています。しかし、すべてが正しい順序で起こっているように見えます。 –

+0

これは実際に起こっていることです。トランザクション(および関連する 'EntityManager')はスレッドベースであり、動作しません。もちろん、JMSの考え方はどれですか?この点については意味をなさないので、答えを削除します(あなたのテストで実際にJMSを使用していることは忘れています)。それにもかかわらず、私はまだ**欲求**の使用を提案します。 –

関連する問題