我正在尝试对我在Spring中创建的自定义事件进行单元测试,并且遇到了一个有趣的问题。如果我创建一个StaticApplicationContext
并手动注册并连接bean,我可以触发事件并看到程序流通过发布者(实现ApplicationEventPublisherAware
)到监听器(实现ApplicationListener<?>
)。
然而,当我尝试使用SpringJunit4ClassRunner
和@ContextConfiguration
创建一个JUnit测试来创建上下文时,一切正常,除了ApplicationEvents没有出现在监听器中(我已经确认它们是出版)。
是否有其他方法可以创建上下文,以便ApplicationEvents正常工作?我在网上找不到关于Spring事件框架单元测试的内容。
答案 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 :(得分: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验证事件的内容。