单元测试Spring ApplicationEvents - 事件是否已发布但侦听器未触发?

时间:2011-10-26 16:17:44

标签: spring

我正在尝试对我在Spring中创建的自定义事件进行单元测试,并且遇到了一个有趣的问题。如果我创建一个StaticApplicationContext并手动注册并连接bean,我可以触发事件并看到程序流通过发布者(实现ApplicationEventPublisherAware)到监听器(实现ApplicationListener<?>)。

然而,当我尝试使用SpringJunit4ClassRunner@ContextConfiguration创建一个JUnit测试来创建上下文时,一切正常,除了ApplicationEvents没有出现在监听器中(我已经确认它们是出版)。

是否有其他方法可以创建上下文,以便ApplicationEvents正常工作?我在网上找不到关于Spring事件框架单元测试的内容。

3 个答案:

答案 0 :(得分:1)

事件不会触发,因为您的测试类未从spring应用程序上下文(事件发布者)注册和解析。

我已经为此实现了一种解决方法,其中事件在另一个类中处理,该类在Spring中作为bean注册并作为测试的一部分进行解析。它不是很漂亮,但是在浪费了一天中最好的部分试图找到更好的解决方案后,我现在很高兴。

我的用例是在RabbitMQ使用者中收到消息时触发事件。它由以下内容组成:

包装类

注意从测试中调用的 Init()函数,在从测试中的容器中解析后传递回调函数

public class TestEventListenerWrapper {

CountDownLatch countDownLatch;
TestEventWrapperCallbackFunction testEventWrapperCallbackFunction;

public TestEventListenerWrapper(){

}

public void Init(CountDownLatch countDownLatch, TestEventWrapperCallbackFunction testEventWrapperCallbackFunction){

    this.countDownLatch = countDownLatch;
    this.testEventWrapperCallbackFunction = testEventWrapperCallbackFunction;
}

@EventListener
public void onApplicationEvent(MyEventType1 event) {

    testEventWrapperCallbackFunction.CallbackOnEventFired(event);
    countDownLatch.countDown();
}

@EventListener
public void onApplicationEvent(MyEventType2 event) {

    testEventWrapperCallbackFunction.CallbackOnEventFired(event);
    countDownLatch.countDown();
}

@EventListener
public void onApplicationEvent(OnQueueMessageReceived event) {

    testEventWrapperCallbackFunction.CallbackOnEventFired(event);
    countDownLatch.countDown();
}
}

回调界面

public interface TestEventWrapperCallbackFunction {

void CallbackOnEventFired(ApplicationEvent event);
}

测试配置类,用于定义单元测试中引用的bean。在此之前有用之前,需要从applicationContext解析并初始化(参见下一步)

    @Configuration
public class TestContextConfiguration {
    @Lazy
    @Bean(name="testEventListenerWrapper")
    public TestEventListenerWrapper testEventListenerWrapper(){
        return new TestEventListenerWrapper();
    }
}

最后,单元测试本身从applicationContext解析bean并调用 Init()函数来传递断言条件(这假设您已将bean注册为单例 - Spring applicationContext的默认值。回调函数在此定义,并传递给 Init()。

    @ContextConfiguration(classes= {TestContextConfiguration.class,
                                //..., - other config classes
                                //..., - other config classes
                                })
public class QueueListenerUnitTests
        extends AbstractTestNGSpringContextTests {

    private MessageProcessorManager mockedMessageProcessorManager;
    private ChannelAwareMessageListener queueListener;

    private OnQueueMessageReceived currentEvent;

    @BeforeTest
    public void Startup() throws Exception {

        this.springTestContextPrepareTestInstance();
        queueListener = new QueueListenerImpl(mockedMessageProcessorManager);
        ((QueueListenerImpl) queueListener).setApplicationEventPublisher(this.applicationContext);
        currentEvent = null;
    }

    @Test
    public void HandleMessageReceived_QueueMessageReceivedEventFires_WhenValidMessageIsReceived() throws Exception {

        //Arrange
        //Other arrange logic
        Channel mockedRabbitmqChannel = CreateMockRabbitmqChannel();
        CountDownLatch countDownLatch = new CountDownLatch(1);

        TestEventWrapperCallbackFunction testEventWrapperCallbackFunction = (ev) -> CallbackOnEventFired(ev);
        TestEventListenerWrapper testEventListenerWrapper = (TestEventListenerWrapper)applicationContext.getBean("testEventWrapperOnQueueMessageReceived");
        testEventListenerWrapper.Init(countDownLatch, testEventWrapperCallbackFunction);

        //Act
        queueListener.onMessage(message, mockedRabbitmqChannel);
        long awaitTimeoutInMs = 1000;
        countDownLatch.await(awaitTimeoutInMs, TimeUnit.MILLISECONDS);

        //Assert - assertion goes here
    }

    //The callback function that passes the event back here so it can be made available to the tests for assertion
    private void CallbackOnEventFired(ApplicationEvent event){
        currentEvent = (OnQueueMessageReceived)event;
    }
}
  • 编辑1 :示例代码已使用CountDownLatch更新
  • 编辑2 :断言没有通过测试失败,所以上面的内容采用了不同的方法进行了更新**

答案 1 :(得分:0)

您可以手动创建上下文。

例如:我需要检查我的ApplicationListener<ContextClosedEvent>是否已关闭Cassandra连接:

@Test
public void testSpringShutdownHookForCassandra(){
    ConfigurableApplicationContext ctx = new AnnotationConfigApplicationContext(CassandraConfig.class);

    CassandraConnectionManager connectionManager = ctx.getBean(CassandraConnectionManager.class);
    Session session = connectionManager.openSession(testKeySpaceName);

    Assert.assertFalse( session.isClosed() );
    ctx.close();

    Assert.assertTrue( session.isClosed() );
}

答案 2 :(得分:0)

我只是将我的应用程序作为SpringBootTest运行,应用程序事件运行正常:

@TestComponent
public class EventTestListener {

    @EventListener
    public void handle(MyCustomEvent event) {
        // nothing to do, just spy the method...
    }
}

@RunWith(SpringRunner.class)
@SpringBootTest
public class MyEventTest {

    @SpyBean
    private EventTestListener testEventListener;

    @Test
    public void testMyEventFires() {
        // do something that fires the event..

        verify(testEventListener).handle(any(MyCustomEvent.class));
    }
}

使用@Captor / ArgumentCaptor验证事件的内容。