Simple way to test Spring onApplicationEvent
Asked Answered
F

2

8

I have an application event listener which contains the function:

@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
//do some stuff
}

How can I write a unit test to simulate a ContextRefreshedEvent, like my jar being executed, and test that my onApplicationEvent function did what it's supposed to do?

Fatidic answered 20/4, 2017 at 18:35 Comment(1)
i just answered a similar question #52085855Intermix
R
9

Here is the smallest standalone example I can Think of.

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.Bean;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.concurrent.LinkedBlockingQueue;

import static org.junit.Assert.assertEquals;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {RefreshTest.MyConfig.class})
public class RefreshTest {

    @Autowired
    private MyListener listener;

    @Test
    public void test() {
        assertEquals("Refresh should be called once",1, listener.events.size());
    }

    public static class MyConfig {
        @Bean
        public MyListener listener() {
            return new MyListener();
        }
    }

    public static class MyListener implements ApplicationListener <ContextRefreshedEvent> {
        // you don't really need a threadsafe collection in a test, as the test main thread is also loading the spring contest and calling the handler, 
        // but if you are inside an application, you should be aware of which thread is calling in case you want to read the result from another thread. 
        LinkedBlockingQueue<ContextRefreshedEvent> events = new LinkedBlockingQueue<ContextRefreshedEvent>();
        public void onApplicationEvent(ContextRefreshedEvent  event) {
            events.add(event);
        }
    }
}

There is a difference between testing the code inside the handler and that the handler is called. Don't put code directly in the handler, but it in another bean, that is invoked from the handler, so you can test the logic without the handler (by calling it with an ContextRefreshedEventyou created). A refresh event is sent when the context is refreshed (typically when it is loaded), so there is no need to test that. If it doesn't get called in your production code, you will usually notice immediately. Also the loading of context may be different between tests and production, so even if you write a test that shows the handler is called, there is no guarantee it will be called in production, unless you are running with exact same @Configuration- Which I almost never do, since I often end up having different implementation of some configurations/bean using @Profile for instance when I don't want my tests to use AWS Queues, and other external IO channels.

Rejoinder answered 20/4, 2017 at 19:51 Comment(0)
F
2

Programmatic Payload Listener Mock Test

I implemented a programmatic payload listener test, based on this spring boot example, if probably someone needs a mock test instead of loading all the spring context.

@Test
public void testProgrammaticEventListener() {
    final List<StoreEvent> events = new ArrayList<>();
    final ApplicationListener<PayloadApplicationEvent<Integer>> listener = forPayload(events::add);

    ConfigurableApplicationContext ac = new GenericApplicationContext();
    ac.addApplicationListener(listener);
    ac.refresh();

    PayloadApplicationEvent<Integer> event = new PayloadApplicationEvent<>(this, 1);
    ac.publishEvent(event);
    assertThat(events.contains(event.getPayload()), Is.is(true));
}

In the example code, there is a method in the ApplicationListener.forPayload; if it doesn't exists, you can add manually to your test class:

static <T> ApplicationListener<PayloadApplicationEvent<T>> forPayload(final Consumer<T> consumer) {
        return event -> consumer.accept(event.getPayload());
    }

Reference PayloadApplicationEventTests.java#L65

Fugazy answered 22/1, 2021 at 14:43 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.