Simple way to test Spring onApplicationEvent

yalpsid eman picture yalpsid eman · Apr 20, 2017 · Viewed 7.2k times · Source

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?

Answer

Klaus Groenbaek picture Klaus Groenbaek · Apr 20, 2017

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.