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.
Related
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!");
}
}
}
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 have an application that listens for Kafka messages using #KafkaListener inside of a #Component. Now I'd like to make an integration test with a Kafka test container (which spins up Kafka in the background). In my test I want to verify that the listener method was called and finished, however when I use #SpyBean in my test I get:
No bean found for definition [SpyDefinition#7a939c9e name = '', typeToSpy = com.demo.kafka.MessageListener, reset = AFTER]
I'm using Kotling, important classes:
Class to test
#Component
class MessageListener(private val someRepository: SomeRepository){
#KafkaListener
fun listen(records: List<ConsumerRecord<String, String>>) {
// do something with someRepository
}
}
Base test class
#ExtendWith(SpringExtension::class)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
#TestInstance(TestInstance.Lifecycle.PER_CLASS)
class KafkaContainerTests {
// some functionality to spin up kafka testcontainer
}
Test class
class MessageListenerTest #Autowired constructor(
private val someRepository: SomeRepository
) : KafkaContainerTests() {
#SpyBean
private lateinit var messageListenerSpy: MessageListener
private var messageListenerLatch = CountDownLatch(1)
#BeforeAll
fun setupLatch() {
logger.debug("setting up latch")
doAnswer {
it.callRealMethod()
messageListenerLatch.count
}.whenever(messageListenerSpy).listen(any())
}
#Test
fun testListener(){
sendKafkaMessage(someValidKafkaMessage)
// assert that the listen method is being called & finished
assertTrue(messageListenerLatch.await(20, TimeUnit.SECONDS))
// and assert someRepository is called
}
}
The reason I am confused is that when I add the MessageListener to the #Autowired constructor of the MessageListenerTest it does get injected successfully.
Why is the test unable to find the bean when using #SpyBean?
It works fine for me with Java:
#SpringBootTest
class So58184716ApplicationTests {
#SpyBean
private Listener listener;
#Test
void test(#Autowired KafkaTemplate<String, String> template) throws InterruptedException {
template.send("so58184716", "foo");
CountDownLatch latch = new CountDownLatch(1);
willAnswer(inv -> {
inv.callRealMethod();
latch.countDown();
return null;
}).given(this.listener).listen("foo");
assertThat(latch.await(10, TimeUnit.SECONDS)).isTrue();
verify(this.listener).listen("foo");
}
}
#SpringBootApplication
public class So58184716Application {
public static void main(String[] args) {
SpringApplication.run(So58184716Application.class, args);
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so58184716").partitions(1).replicas(1).build();
}
}
#Component
class Listener {
#KafkaListener(id = "so58184716", topics = "so58184716")
public void listen(String in) {
System.out.println(in);
}
}
I have moved a synchronous process to asynchronous, and now I have some troubles to maintain integration tests. It seems related to the fact that when you create a new thread inside a #Transactional method, then call a new #Transactional, Spring create a new transaction.
During integration tests the problem occurs with #Transactional tests. It seems that the thread transaction is rollbacked before the test finishes because of TransactionalTestExecutionListener in test configuration.
I'have tried many things like
- autowiring EntityManager and manually flushing after thread was finished
- using #Rollback instead of #Transactional in test methods
- managing transactions with TestTransaction
- using #Rollback and TestTransaction together
Here is the simplified source code :
public interface MyService{
public void doThing(someArgs...);
public void updateThings(someArgs...);
}
#Service
public class MyServiceImpl implements MyService{
#Autowired
private AsynchronousFutureHandlerService futureService;
#Autowired
#Qualifier("myExecutorService")
private ScheduledExecutorService myExecutorService;
#Transactional
#Override
public void doThing(someArgs...){
doThingAsync(someArgs...);
}
private void doThingAsync(someArgs...){
AsynchronousHandler runnable = applicationContext.getBean(
AsynchronousHandler.class, someArgs...);
//as we are executing some treatment in a new Thread, a new transaction is automatically created
Future<?> future = myExecutorService.submit(runnable);
//keep track of thread execution
futureService.addFutures(future);
}
#Override
#Transactional
public void updateThings(someArgs...){
//do udpate stuff
}
}
/**
* very basic solution to improve later to consult thread state
*/
#Service
public class AsynchronousFutureHandlerService {
//TODO : pass to ThreadSafe collection
private List<Future<?>> futures = new ArrayList<>();
public void addTheoreticalApplicationFuture(Future<?> future){
futures.add(future);
this.deleteJobsDone();
}
public boolean isThreadStillRunning(){
boolean stillRunning = false;
for(Future<?> f : futures){
if(!f.isDone()){
stillRunning = true;
break;
}
}
return stillRunning;
}
public void deleteJobsDone(){
this.futures.removeIf(f -> f.isDone());
}
}
#Component
#Scope("prototype")
public class AsynchronousHandler implements Runnable {
#Autowired
private MyService myService;
#Override
public void run() {
myService.updateThings(...); //updates data in DB
...
}
}
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = TestConfiguration.class)
#TestExecutionListeners({ DependencyInjectionTestExecutionListener.class, DataSetTestExecutionListener.class,
TransactionalTestExecutionListener.class })
#DataSet(dbType = DBType.H2, locations = { "classpath:dataset.xml" })
public class MyServiceTest{
#Autowired
private MyService myService;
#Autowired
private AsynchronousFutureHandlerService futureService;
#Test
#Transactional
public void test_doThings(){
myService.doThings(someArgs...);
waitUpdateFinish();
Assert.assertEquals(...); //fails here because Thread transaction has been rollbacked
}
private void waitUpdateFinish() throws InterruptedException{
while(futureService.isThreadStillRunning()){
Thread.sleep(500);
}
}
}
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.