Skip to content

@RabbitListenerTest doesn't work anymore #969

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

Closed
andrei-ivanov opened this issue Apr 5, 2019 · 4 comments
Closed

@RabbitListenerTest doesn't work anymore #969

andrei-ivanov opened this issue Apr 5, 2019 · 4 comments
Assignees

Comments

@andrei-ivanov
Copy link

Affects Version(s): 2.1.5


I've just upgraded to Spring AMQP 2.1.5, triggered automatically by upgrading to Spring Boot 2.1.4 (and Spring Framework 5.1.6), and now my integration test isn't working anymore.

With spring.main.allow-bean-definition-overriding=true:

@RunWith(SpringRunner.class)
@SpringBootTest(properties = "spring.main.allow-bean-definition-overriding=true")
@ImportAutoConfiguration({MockSecurityConfig.class,
                          TransactionManagementTestConfig.class})
@AutoConfigureTestDatabase
@ActiveProfiles({"test", "integration-test"})
public class SalesTransactionGatewayTest {
    @Inject
    private RabbitTemplate template;

    @Inject
    private RabbitListenerTestHarness harness;

    @TestConfiguration
    @RabbitListenerTest(spy = false, capture = true)
    public static class Config {
        @Bean
        public SystemLauncher broker() throws Exception {
            SystemLauncher broker = new SystemLauncher();
            Map<String, Object> attributes = new HashMap<>();
            attributes.put(SystemConfig.TYPE, "Memory");
            attributes.put(SystemConfig.INITIAL_CONFIGURATION_LOCATION, "classpath:qpid-config.json");
            attributes.put(SystemConfig.STARTUP_LOGGED_TO_SYSTEM_OUT, false);
            broker.startup(attributes);
            return broker;
        }
    }
}
org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.amqp.rabbit.test.RabbitListenerTestHarness' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax.inject.Inject()}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1654)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1213)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1167)
	at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:593)

Seeing that this was caused by PR #915 for issue #914, which was asking for tests to work without bean definition overriding enabled, I switched that to false, but that also fails:

org.springframework.beans.factory.BeanDefinitionStoreException: Invalid bean definition with name 'org.springframework.amqp.rabbit.config.internalRabbitListenerAnnotationProcessor' defined in org.springframework.amqp.rabbit.test.RabbitListenerTestBootstrap: @Bean definition illegally overridden by existing bean definition: Root bean: class [org.springframework.amqp.rabbit.annotation.RabbitListenerAnnotationBeanPostProcessor]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.isOverriddenByExistingDefinition(ConfigurationClassBeanDefinitionReader.java:309)
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForBeanMethod(ConfigurationClassBeanDefinitionReader.java:202)
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitionsForConfigurationClass(ConfigurationClassBeanDefinitionReader.java:141)
	at org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader.loadBeanDefinitions(ConfigurationClassBeanDefinitionReader.java:117)
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.processConfigBeanDefinitions(ConfigurationClassPostProcessor.java:327)
	at org.springframework.context.annotation.ConfigurationClassPostProcessor.postProcessBeanDefinitionRegistry(ConfigurationClassPostProcessor.java:232)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanDefinitionRegistryPostProcessors(PostProcessorRegistrationDelegate.java:275)
	at org.springframework.context.support.PostProcessorRegistrationDelegate.invokeBeanFactoryPostProcessors(PostProcessorRegistrationDelegate.java:95)
	at org.springframework.context.support.AbstractApplicationContext.invokeBeanFactoryPostProcessors(AbstractApplicationContext.java:705)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:531)
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
	at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:127)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:99)
	at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:117)
@artembilan artembilan self-assigned this Apr 5, 2019
@artembilan
Copy link
Member

artembilan commented Apr 5, 2019

Works for me with Boot-2.1.4 and its transitive Spring AMPQ, which is indeed 2.1.5:

@RunWith(SpringRunner.class)
@SpringBootTest
public class BootRabbitListenerTestApplicationTests {

	@Autowired
	private RabbitTemplate rabbitTemplate;

	@Autowired
	private Queue queue1;

	@Autowired
	private RabbitListenerTestHarness harness;

	@Test
	public void testTwoWay() throws Exception {
		assertEquals("FOO", this.rabbitTemplate.convertSendAndReceive(this.queue1.getName(), "foo"));

		RabbitListenerTestHarness.InvocationData invocationData =
				this.harness.getNextInvocationDataFor("foo", 10, TimeUnit.SECONDS);
		assertNotNull(invocationData);
		assertThat(invocationData.getArguments()[0], equalTo("foo"));
		assertThat(invocationData.getResult(), equalTo("FOO"));
	}

	@TestConfiguration
	@RabbitListenerTest(spy = false, capture = true)
	public static class Config {

		@Bean
		public Queue queue1() {
			return new AnonymousQueue();
		}


		@RabbitListener(id = "foo", queues = "#{queue1.name}")
		public String foo(String foo) {
			return foo.toUpperCase();
		}

	}

}

I have nothing more in my project though, just only this test class and an empty @SpringBootApplication class.
I have only spring-boot-starter-amqp, spring-boot-starter-test and spring-rabbit-test dependencies.

so, something fishy is in your project.

Would be great to have simple variant of that so we can reproduce and play with on our side.

Thanks for understanding.

@andrei-ivanov
Copy link
Author

andrei-ivanov commented Apr 9, 2019

Hmm, I think I found the root cause.
In the SalesTransactionGatewayTest, there's an integration-test profile activated, which includes this class:

@Configuration
@Profile("integration-test")
@EnableRabbit
public class TestAmqpConfiguration {
    private static final Logger LOGGER = Logger.getLogger(TestAmqpConfiguration.class.getName());

    static final String TRANSACTIONS_LISTENER = "transactions";

    @Bean
    public MessageRecoverer messageRecoverer(AmqpTemplate amqpTemplate) {
        // will send the error details to a queue named `error.${queues.transactions}`
        return new RepublishMessageRecoverer(amqpTemplate);
    }

    @RabbitListener(id = TRANSACTIONS_LISTENER, queues = "${queues.transactions}", returnExceptions = "true")
    public void receive(SalesTransaction transaction) {
        LOGGER.log(Level.INFO, "Received tx: {0}", transaction);
    }

    @Configuration
    @ConditionalOnBean(name = "rabbitListenerContainerFactory")
    public static class TestConfigurationInitializer {
        private final AbstractRabbitListenerContainerFactory rabbitListenerContainerFactory;
        private final PlatformTransactionManager txManager;

        public TestConfigurationInitializer(AbstractRabbitListenerContainerFactory rabbitListenerContainerFactory,
                                            PlatformTransactionManager txManager) {
            this.rabbitListenerContainerFactory = rabbitListenerContainerFactory;
            this.txManager = txManager;
        }

        @PostConstruct
        void init() {
            LOGGER.log(Level.INFO,
                       "Setting tx manager {0} on the rabbitListenerContainerFactory {1}",
                       new Object[]{txManager, rabbitListenerContainerFactory});
            rabbitListenerContainerFactory.setTransactionManager(txManager);
            rabbitListenerContainerFactory.setChannelTransacted(Boolean.TRUE);
        }
    }
}

As the profile name says, it's used to perform an integration test at runtime in our test environments, so I've thought I should reuse it so the listener defined here was invoked.

The problem seems to be caused by the fact that this TestAmqpConfiguration is annotated with @EnableRabbit which triggers RabbitBootstrapConfiguration#registerBeanDefinitions , which doesn't find the annotation processor and registers it before RabbitListenerTestBootstrap gets a chance to run.

Is there a way to make this kind of setup work?

@artembilan
Copy link
Member

Doesn't look like you need that @EnableRabbit because your tests are based on Spring Boot, which will bring an appropriate auto-configuration for Spring AMQP as well.

On the other hand it is not clear, what and how you want to reuse (too much custom code), so, I wouldn't rely on that external configuration, but just copy/pasted whatever I need in my current test for better isolation and to avoid some conflicts in the future when that class may be modified and it will affect your one as well.

Doesn't look like a problem of the Framework, but more design flaw in the target project.
Since we can't do anything from our side to mitigate such an issue in the target project, I close this as Works as Designed.

Thanks for understanding.

@andrei-ivanov
Copy link
Author

Indeed, your solution worked, removing @EnableRabbit made it work. 😄
I see now it gets enabled automatically if the annotation processor is missing.

Sorry for the noise.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants