Device Testing Spring ApplicationEvents - Events are posted, but listeners don't shoot?

I am trying to create unit test custom events that I created in Spring and I am having an interesting problem. If I create StaticApplicationContextand register manually and connect beans, I can trigger events and view the program stream through the publisher (implements ApplicationEventPublisherAware) to the listener (implements ApplicationListener<?>).

However, when I try to create a JUnit test to create a context using SpringJunit4ClassRunnerand @ContextConfiguration, everything works fine, except that ApplicationEvents are not displayed in the listener (I confirmed that they are published).

Is there any other way to create a context so that ApplicationEvents works correctly? I have not found much on the Internet about the Spring event structure testing module.

+5
source share
3 answers

Events do not fire because your test classes are not logged or resolved from the context of the spring application, which is the event publisher.

I applied a workaround for this case when the event is handled in another class that is registered in spring as a bean and resolved as part of the test. This is ugly, but having spent most of the day trying to find the best solution, I am satisfied with it for now.

My use case raised an event when a message was received inside a RabbitMQ consumer. It consists of the following:

Shell class

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, unit test. , applicationContext initialsed (. )

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

, unit test, bean applicationContext Init(), ( , bean singleton - 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

.

: , 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() );
}
0

How about this:

@TestComponent
public class EventTestListener {

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

public class MyEventTest {

    @SpyBean
    private EventTestListener testEventListener;

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

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

use @ Captor / ArgumentCaptor to check the contents of your event.

0
source

All Articles