Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AmqpException when testing @RabbitListener with @RabbitHandler #2456

Closed
kstojanovski-novatec opened this issue May 19, 2023 · 12 comments · Fixed by #2457
Closed

AmqpException when testing @RabbitListener with @RabbitHandler #2456

kstojanovski-novatec opened this issue May 19, 2023 · 12 comments · Fixed by #2457

Comments

@kstojanovski-novatec
Copy link

In what version(s) of Spring AMQP are you seeing this issue?

3.0.6

Describe the bug

I am not sure if it is a bug but I can not explain the thrown exception Caused by: org.springframework.amqp.AmqpException: Ambiguous methods for payload type for the given situation.

On integration test with test containers and spying a class with Mockito annotated with @RabbitListener and its methods annotated with @RabbitHandler leads to AmqpException.

Obviously, the original @RabbitHanlder method is loaded and the mocked one at the same time.
MessagingMessageListenerAdapter::invokeHandlerAndProcessResult at line 140 (this.handlerAdapter.delegatingHanlder)

    if (this.messagingMessageConverter.method == null && amqpMessage != null) {
      amqpMessage.getMessageProperties().setTargetMethod(this.handlerAdapter.getMethodFor(message.getPayload()));
    }

It there anything I can do to avoid this situation?

To Reproduce

You need to start the tests which are located in the linked project.

Expected behavior

I would not expect the error since the methods are not loaded twice.

Sample

Starting the DifferentTypesDistributorListenerIntegrationTest or ManyCustomersWorkersSpyListenerIntegrationTest from the project led to the exception https://github.com/kstojanovski-novatec/spring-amqp-testing-issue.

@artembilan
Copy link
Member

Obviously, the original @RabbitHanlder method is loaded and the mocked one at the same time.

Might indeed be a case.
Not sure how to fix such a situation since you are indeed expecting only those stubs to be available for your testing.

Well, let's see if @RabbitListenerTest can help you somehow: https://docs.spring.io/spring-amqp/reference/html/#test-harness.

Your project is too complex to give an immediate advice...

@kstojanovski-novatec
Copy link
Author

Hello Artem, thanks for the fast answer.

I saw the linked documentation and have also downloaded the four files from the GitHub repo:

  • org.springframework.amqp.rabbit.test.examples.ExampleRabbitListenerCaptureTest
  • org.springframework.amqp.rabbit.test.examples.ExampleRabbitListenerSpyAndCaptureTest
  • org.springframework.amqp.rabbit.test.examples.ExampleRabbitListenerSpyTest
  • org.springframework.amqp.rabbit.test.examples.TestRabbitTemplateTests

Only TestRabbitTemplateTests works well, but this kind of test I already have. I am interested in the other test where RabbitListenerTestHarness is used.
Unfortunately the private RabbitListenerTestHarness harness; is marked red with the message "Could not auto-wire. No beans of 'RabbitListenerTestHarness' type found." for the other three classes as well.

Then I downloaded the whole project Spring AMQP(spring-amqp-dist) and there the reference is also marked with the same message. I have tried to debug the tests and only TestRabbitTemplateTests could be debugged. The other tests are skipped. Should that be that way?

@artembilan
Copy link
Member

The harness as red is just a flaw of an IDE.
That bean is provided by the framework and IDE does not have an ability to scan classpath for manually registered beans like it happens with this one via RabbitListenerTestBootstrap.

Those tests does not pass for your because you don't have a RabbitMQ broker on the default host/port.
See that @RabbitAvailable what all those tests are marked with.
In other words: the listener tests with mocks and spies still require a real RabbitMQ to interact with.

However you probably can combine both TestRabbitTemplate and RabbitListenerTestHarness.

@garyrussell
Copy link
Contributor

Perhaps that is just a warning from your IDE because it doesn't understand what the @RabbitListenerTest annotation does; you can ignore it.

@garyrussell
Copy link
Contributor

Reproduced by returning a spy here:

@Bean
public MultiListenerBean multiListener() {
return new MultiListenerBean();
}

As stated, all methods are doubled up; in this case I get

java.lang.IllegalStateException: Failed to load ApplicationContext for [MergedContextConfiguration@41bb1f09 testClass = org.springframework.amqp.rabbit.annotation.EnableRabbitIntegrationTests, locations = [], classes = [org.springframework.amqp.rabbit.annotation.EnableRabbitIntegrationTests.EnableRabbitConfig], contextInitializerClasses = [], activeProfiles = [], propertySourceLocations = [], propertySourceProperties = ["spring.application.name=testConnectionName"], contextCustomizers = [], contextLoader = org.springframework.test.context.support.DelegatingSmartContextLoader, parent = null]
...
Caused by: java.lang.IllegalStateException: Only one @RabbitHandler can be marked 'isDefault', found: public java.lang.String org.springframework.amqp.rabbit.annotation.EnableRabbitIntegrationTests$MultiListenerBean$MockitoMock$GPAkOvLI.defaultHandler(java.lang.Object) and public java.lang.String org.springframework.amqp.rabbit.annotation.EnableRabbitIntegrationTests$MultiListenerBean.defaultHandler(java.lang.Object)
	at org.springframework.util.Assert.state(Assert.java:97)
	at org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor.processMultiMethodListeners(RabbitListenerAnnotationBeanPostProcessor.java:368)
	at org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor.postProcessAfterInitialization(RabbitListenerAnnotationBeanPostProcessor.java:320)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:434)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1773)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598)
	... 85 more

Looks like we need to add code to the BPP to ignore methods from classes containing @MockitoMock...

Spy method:

public java.lang.String org.springframework.amqp.rabbit.annotation.EnableRabbitIntegrationTests$MultiListenerBean$MockitoMock$m0IRDAeN.defaultHandler(java.lang.Object)

Real method:

public java.lang.String org.springframework.amqp.rabbit.annotation.EnableRabbitIntegrationTests$MultiListenerBean.defaultHandler(java.lang.Object)

@artembilan
Copy link
Member

But if we do that then purpose of the spy() will be eliminated. No?

@garyrussell garyrussell added this to the 3.0.5 milestone May 22, 2023
@garyrussell
Copy link
Contributor

Probably, yes; need to figure out what works (might need to filter out the non-mock methods) - but I am currently seeing another strange side-effect of making the bean a spy - when the queue name is anonymous, I am getting 2 containers/anonymous queues. If I name the queue, I only get one container. Very strange.

@garyrussell
Copy link
Contributor

I am off work tomorrow so may have to revisit this later this week.

@garyrussell
Copy link
Contributor

The duplicate container issue is because we see 2 class level @RabbitListeners.

@garyrussell
Copy link
Contributor

It all works fine; PR soon.

garyrussell added a commit to garyrussell/spring-amqp that referenced this issue May 22, 2023
Resolves spring-projects#2456

When spying a `@RabbitListener` bean, duplicate methods are resolved as well
as duplicate class level `@RabbitListener` annotations.

**cherry-pick to 2.4.x** (will require instanceof polishing for Java 8)
artembilan pushed a commit that referenced this issue May 22, 2023
Resolves #2456

When spying a `@RabbitListener` bean, duplicate methods are resolved as well
as duplicate class level `@RabbitListener` annotations.

**cherry-pick to 2.4.x** (will require instanceof polishing for Java 8)
artembilan pushed a commit that referenced this issue May 22, 2023
Resolves #2456

When spying a `@RabbitListener` bean, duplicate methods are resolved as well
as duplicate class level `@RabbitListener` annotations.

**cherry-pick to 2.4.x** (will require instanceof polishing for Java 8)
@kstojanovski-novatec
Copy link
Author

Thanks for the work, Gary and Artem.

I wrote I found that issue in version 3.0.6, but it was the version of the pring-boot-starter-parent project. The version of the spring-amqp-dist project used there is 3.0.4, and of course, its modules, but the change is committed in the 3.0.5-SNAPSHOT.

When will the modification be part of the spring-boot-starter-amqp?

@garyrussell
Copy link
Contributor

June 19, with Boot to follow a couple of days later.

https://github.com/spring-projects/spring-amqp/milestones

https://calendar.spring.io/

garyrussell added a commit to garyrussell/spring-amqp that referenced this issue Sep 25, 2023
The framework is intended for use with Spring Framework 5.3.

However, this change was the only thing preventing it being used
with 5.2.
garyrussell added a commit that referenced this issue Sep 25, 2023
The framework is intended for use with Spring Framework 5.3.

However, this change was the only thing preventing it being used
with 5.2.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants