I wanted to test case that involves Spring application events. I don't see my EventListener code executed. I have create a simple test case. It doesn't work either. Is this a limitation of the Spring Events that it doesn't work in tests?
When I debug the code I can see that the publisher was replaced by some kind of mock.
#SpringBootTest(classes = {Application.class, PubListTest.class})
#Slf4j
#Configuration
class PubListTest {
#Autowired
TestClass testClass;
#Test
#SneakyThrows
void test() {
testClass.publish();
}
#RequiredArgsConstructor
#Component
public static class TestClass {
private final ApplicationEventPublisher publisher;
public void publish() {
publisher.publishEvent(new UserGroupDeleted(UUID.randomUUID(), Set.of()));
}
#EventListener
public void onUserGroupEvent(UserGroupDeleted event) {
System.out.println("TEST!");
}
}
}
Related
I have the following publishing class.
#Component
public class Publisher {
#Autowired
private MessageChannel publishingChannel;
#Override
public void publish(Event event) {
publishingChannel.send(event);
}
}
I have the following test class.
#RunWith(SpringRunner.class)
#SpringBootTest
public class PublisherTest {
private final List<Event> events = new ArrayList<>();
#Autowired
private Publisher publisher;
#Test
public void testPublish() {
Event testEvent = new Event("some_id_number");
publisher.publish(testEvent);
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(() -> !this.events.isEmpty());
}
#ServiceActivator(inputChannel = "publishingChannel")
public void publishEventListener(Event event) {
this.events.add(event);
}
}
The message channel bean is instantiated elsewhere. The publisher runs as expected and an event is publishing to the channel, however the service activator is never invoked. What am I missing here?
Turns out you need to move the service activator to a separate test component class (#TestComponent prevents this from being injected outside the test context).
#TestComponent
public class TestListener {
public final List<Object> results = new ArrayList<>();
#ServiceActivator(inputChannel = "messageChannel")
public void listener(Event event) {
Object id = event.getHeaders().get("myId");
results.add(id);
}
}
Then you can bring this listener into your test. Make sure you use #Import to bring your service activator class into the test context.
#SpringBootTest
#Import(TestListener.class)
class PublisherTest {
#Autowired
private Publisher publisher;
#Autowired
private TestListener testListener;
#Test
void testPublish() {
this.publisher.publish(new Event().addHeader("myId", 1));
Awaitility.await()
.atMost(2, TimeUnit.SECONDS)
.until(() -> !this.testListeners.results.isEmpty());
}
}
The test passes after making these changes. Figured this out with a demo app and applied it to a production testing issue.
I have a dummy project where I try figure out how to test pointcuts being triggered.
My project consists of 1 aspect bean which just prints after a foo method is called
#Component
#Aspect
public class SystemArchitecture {
#After("execution(* foo(..))")
public void after() {
System.out.println("#After");
}
}
And a FooServiceImpl with implemented foo method
#Service
public class FooServiceImpl implements FooService{
#Override
public FooDto foo(String msg) {
return new FooDto(msg);
}
}
The code works and and I can see "#After" being printed to console, but I can't check programatically if after pointcut was called using the test below.
#SpringBootTest
public class AspectTest {
#Autowired
private FooService fooService;
#Test
void shouldPass() {
fooService.foo("hello");
}
}
I've also tried using non-bean proxy as was adviced in https://stackoverflow.com/a/56312984/18224588, but this time I'm getting an obvious error cannot extend concrete aspect because my spy proxy is no longer viewed as an aspect:
public class AspectNoContextTest {
#Test
void shouldPass() {
FooService fooService = Mockito.mock(FooService.class);
SystemArchitecture systemArchitecture = Mockito.spy(new SystemArchitecture());
AspectJProxyFactory aspectJProxyFactory = new AspectJProxyFactory(fooService);
aspectJProxyFactory.addAspect(systemArchitecture);
DefaultAopProxyFactory proxyFactory = new DefaultAopProxyFactory();
AopProxy aopProxy = proxyFactory.createAopProxy(aspectJProxyFactory);
FooService proxy = (FooService) aopProxy.getProxy();
proxy.foo("foo");
verify(systemArchitecture, times(1)).after();
}
}
Ok, after some digging, I found that it's possible to accomplish this by making an aspect a #SpyBean. Also AopUtils can be used for performing additional checks
#SpringBootTest
public class AspectTest {
#Autowired
private FooService fooService;
#SpyBean
private SystemArchitecture systemArchitecture;
#Test
void shouldPass() {
assertTrue(AopUtils.isAopProxy(fooService));
assertTrue(AopUtils.isCglibProxy(fooService));
fooService.foo("foo");
verify(systemArchitecture, times(1)).after();
}
}
I want to integration test a Spring Boot 1.5.4 service that uses an #EventListener. My problem is: when the test is run, the events are correctly published, but they are not consumed.
My ultimate purpose is to use a #TransactionEventListener, but for simplicity I start with an #EventListener.
Here is my service class:
#Service
public class MyService {
private static final Logger logger = // ...
private final ApplicationEventPublisher eventPublisher;
#Autowired
public MyService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
#Transactional
public void publish() {
logger.warn("Publishing!");
eventPublisher.publishEvent(new MyEvent());
}
// #TransactionalEventListener
#EventListener
public void consume(MyEvent event) {
logger.warn("Consuming!"); // this is never executed in the test
}
public static class MyEvent {
}
}
Here is my JUnit test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#SpringBootConfiguration
public class MyServiceIT {
#Autowired
MyService myService;
#Test
public void doSomething() {
myService.publish();
}
static class TestConfig {
#Bean
public MyService myService() {
return new MyService(eventPublisher());
}
#Bean
public ApplicationEventPublisher eventPublisher() {
ApplicationEventPublisher ctx = new GenericApplicationContext();
((AbstractApplicationContext)ctx).refresh();
return ctx;
}
}
}
Note: the call to refresh() prevents an IllegalStateException with message "ApplicationEventMulticaster not initialized - call 'refresh' before multicasting events via the context" from occurring.
Does anyone have a clue? Many thanks in advance.
For the record, the solution was: keep the event consumer method in another bean than the event producer method. That is, extract method consume(MyEvent) from class MyService into a new #Service class MyConsumer.
My configuration class looks like this :
#SpringBootApplication
public class Application {
#Bean(destroyMethod = "close")
public CassandraClient cassandraClient() { ... }
}
My CassandraClient class has a close() method, which is being invoked when the application context shuts down (I see it through step debugging). However, I can't find a way to test that the close() method is effectively called.
Here is what I would like to test :
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#ContextConfiguration(classes = { Application.class })
#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
public class ApplicationIntegrationTests implements ApplicationContextAware {
ApplicationContext applicationContext;
#Autowired
CassandraClient cassandraClient;
#Test
public void cassandraClientCloseIsCalled() {
((ConfigurableApplicationContext)applicationContext).close();
// How can I check that cassandraClient.close() has been called once ?
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
I tried adding an aspect to my configuration class to do the counting, but I can't get a pointcut to match the close method. It seems like my aspect is being destroyed before the cassandraClient bean.
I see this question has been here for a while, I looked into this and found a solution that works for me. You can look at it here: Testing Nuts Example. Essentially, almond is a nut that I create using the nut object. Both the init and the destroy methods are private, so I create an extension in another configuration in order to create a mock and be able to verify it with Mockito. This way I can shadow the private methods and generate public methods for this test. But what I think it's interesting in this case for you is that using the #PreDestroy annotation in my #Configuration for the #Test, I can then test the destroy method, just because at that point, all destroy methods have been called. Here is a copy of my code here just to clarify this:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {NutsConfiguration.class, NutsMethodsConfigurationTest.NutsTestConfiguration.class})
class NutsMethodsConfigurationTest {
#Autowired
public Nut almond;
#MockBean
public static NutExtended nutExtended;
#Autowired
public ApplicationContext applicationContext;
#Test
void almond() {
ConsolerizerComposer.outSpace()
.orange(almond)
.orange(nutExtended)
.reset();
verify(nutExtended, times(1)).initiate();
}
#Configuration
public static class NutsTestConfiguration {
#Bean
#Primary
#Qualifier("nut")
public NutExtended nut() {
return new NutExtended();
}
#PreDestroy
public void check() {
verify(nutExtended, times(1)).goToCake();
}
}
#Configuration
public static class NutExtended extends Nut {
public void goToCake() {
BROWN.printGenericLn("Going to cake...");
}
public void initiate() {
ORANGE.printGenericLn("Creating %s", toString());
}
}
}
I hope this helps 😊!
I am using a Spring #Component which implements
ApplicationListener<ContextRefreshedEvent>. This component runs on tomcat startup, but when I run the unit tests, it runs the Component again. Why is that happening?
Here is the component -
BackGroundServices implements Thread.
#Component
public class RunBackgroundServices implements ApplicationListener<ContextRefreshedEvent> {
private final BackgroundServices backgroundServices;
private ExecutorService executor;
#Autowired
public RunBackgroundServices(BackgroundServices backgroundServices) {
this.backgroundServices= backgroundServices;
}
#Override
public void onApplicationEvent(ContextRefreshedEvent event) {
executor = Executors.newSingleThreadExecutor();
executor.submit(backgroundServices);
}
public void onApplicationEvent(ContextStoppedEvent event) {
executor.shutdown();
}
}
you can annotate the test like that
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { TestConfiguration.class}, loader = AnnotationConfigContextLoader.class)
public class MyTest{}
and then create your own configuration of jUnit tests when you can initialize only these beans that you want
#Configuration
public class TestConfiguration {
#Bean
poublic Xxx xxBean(){
}
}