[Spring Boot] Unit tests fail when RabbitMQ not running

I have a spring boot application with a very simple unit test (in fact, it’s a no-op) which fails every time RabbitMQ is not running when my build runs.

I get the following error:

`

java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘axonConfig’: Injection of autowired dependencies failed; nested exception is org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:867)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:543)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:693)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:360)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.test.context.web.ServletTestExecutionListener.setUpRequestContextIfNecessary(ServletTestExecutionListener.java:189)
at org.springframework.test.context.web.ServletTestExecutionListener.prepareTestInstance(ServletTestExecutionListener.java:131)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.apache.maven.surefire.junit4.JUnit4Provider.execute(JUnit4Provider.java:283)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeWithRerun(JUnit4Provider.java:173)
at org.apache.maven.surefire.junit4.JUnit4Provider.executeTestSet(JUnit4Provider.java:153)
at org.apache.maven.surefire.junit4.JUnit4Provider.invoke(JUnit4Provider.java:128)
at org.apache.maven.surefire.booter.ForkedBooter.invokeProviderInSameClassLoader(ForkedBooter.java:203)
at org.apache.maven.surefire.booter.ForkedBooter.runSuitesInProcess(ForkedBooter.java:155)
at org.apache.maven.surefire.booter.ForkedBooter.main(ForkedBooter.java:103)
Caused by: org.springframework.amqp.AmqpConnectException: java.net.ConnectException: Connection refused
at org.springframework.amqp.rabbit.support.RabbitExceptionTranslator.convertRabbitAccessException(RabbitExceptionTranslator.java:62)
at org.springframework.amqp.rabbit.connection.AbstractConnectionFactory.createBareConnection(AbstractConnectionFactory.java:368)
at org.springframework.amqp.rabbit.connection.CachingConnectionFactory.createConnection(CachingConnectionFactory.java:573)
at org.springframework.amqp.rabbit.core.RabbitTemplate.doExecute(RabbitTemplate.java:1430)
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1411)
at org.springframework.amqp.rabbit.core.RabbitTemplate.execute(RabbitTemplate.java:1387)
at org.springframework.amqp.rabbit.core.RabbitAdmin.declareExchange(RabbitAdmin.java:157)
at com.myco.pkg.AxonConfig.configure(AxonConfig.java:80)

`

The failure is not a big surprise, given my AxonConfig (com.myco.pkg.AxonConfig):

`

package com.myco.pkg;

import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import org.axonframework.commandhandling.AsynchronousCommandBus;
import org.axonframework.common.caching.Cache;
import org.axonframework.common.caching.WeakReferenceCache;
import org.axonframework.common.transaction.TransactionManager;
import org.axonframework.eventsourcing.eventstore.EventStorageEngine;
import org.axonframework.eventsourcing.eventstore.EventStore;
import org.axonframework.eventsourcing.eventstore.inmemory.InMemoryEventStorageEngine;
import org.axonframework.messaging.Message;
import org.axonframework.messaging.annotation.ParameterResolverFactory;
import org.axonframework.messaging.interceptors.BeanValidationInterceptor;
import org.axonframework.spring.eventsourcing.SpringAggregateSnapshotter;
import org.springframework.amqp.core.AmqpAdmin;
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Exchange;
import org.springframework.amqp.core.ExchangeBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.QueueBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AxonConfig {

public static final String EXCHANGE_NAME = “PkgEvents”;
public static final String QUEUE_NAME = EXCHANGE_NAME;

@Bean
public BeanValidationInterceptor<Message<?>> beanValidationInteceptor() {
return new BeanValidationInterceptor<>();
}

@Bean(destroyMethod=“shutdown”)
public AsynchronousCommandBus commandBus() {
AsynchronousCommandBus cb = new AsynchronousCommandBus();
cb.registerDispatchInterceptor(beanValidationInteceptor());
return cb;
}

@Bean
public Cache cache() {
return new WeakReferenceCache();
}

@Bean
public SpringAggregateSnapshotter snapshotter(ParameterResolverFactory parameterResolverFactory,
EventStore eventStore, TransactionManager transactionManager) {
Executor executor = Executors.newSingleThreadExecutor();
return new SpringAggregateSnapshotter(eventStore, parameterResolverFactory, executor, transactionManager);
}

@Bean
public EventStorageEngine eventStorageEngine() {
return new InMemoryEventStorageEngine();
}

@Bean
public Exchange exchange() {
return ExchangeBuilder.fanoutExchange(EXCHANGE_NAME).build();
}

@Bean
public Queue queue() {
return QueueBuilder.durable(QUEUE_NAME).build();
}

@Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with("*").noargs();
}

@Autowired
public void configure(AmqpAdmin admin) {
admin.declareExchange(exchange()); // NOTE: this is line 80, referenced by the stack trace above!!!
admin.declareQueue(queue());
admin.declareBinding(binding());
}
}

`

There is nothing in the configuration that accounts for the unit test scenario. So I need some way to mock the Exchange (org.springframework.amqp.core.Exchange) and probably much more. I can’t find any documentation or any thing in the mailing list or otherwise on the interwebs that speaks to this. I’ve even studied the AxonBank and AxonTrader sample applications to see what they do, and they don’t seem to have any unit tests in the spring boot applications.

FYI, the failing unit test:

`

package com.myco.pkg;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

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

@Test
public void contextLoads() {
}

}

`

I’m not sure why AxonBank/Trader don’t have spring boot application unit tests, but it seems like I am going to want them. I do have my domain split into a separate library project and I use the axon test fixtures to test the commands and events.

What are other people doing for this?

Thanks!

Hi Troy,

I recently came across this: https://www.testcontainers.org. Interesting if you need to perform integration tests using JUnit (which the Spring Boot test actually is).

I haven’t worked with it myself, yet.
Alternatively, you can define a spring @Configuration class in your test and override certain beans.

Cheers,

Allard

Thanks Allard, the testcontainers project looks interesting, I’ll look into it.

Do you know if people are commonly mocking out the RabbitMQ dependencies in order to be able to execute unit tests within the “application” module of their axonframework products? I’ve read some posts about mocking RabbitMQ for spring/java projects and it seems pretty complex, especially since I’m new to RabbitMQ and axonframework. It would be awesome to be able to find an example of an axonframework product that has done this, if this is being done.

On the other hand, I think one can possibly make a case for only integration tests (potentially leveraging something like testcontainers) for a product’s application module. This would have to imply that the application module is very thin and the command and query sides of the product are physically separated from it, so domain logic can be properly unit tested–of course leveraging the axonframework test fixtures. So a basic axonframwork multi-module product may look something like this:

  • Product

  • application module (executable jar artifact, no unit tests)

  • command module (library jar artifact, comprehensive unit tests)

  • query module (library jar artifact, comprehensive unit tests)

  • api module (library jar artifact, comprehensive unit tests)

Please forgive me if this this is so basic and obvious, I’m just trying to determine best practices. What are your thoughts?

Thanks,

Troy

Hi Troy,

the separation is indeed in lines of what I would recommend, with the following points of attention:

  • consider the “application module”, the packaging module. It defines what it part of the deployable unit.
  • there is hardly ever a single “command module”. In many applications, there are many command handling components that don’t need to be deployed as a single unit. In that case, each of these components would be in their own module. Don’t go overboard on separating; keep “conceptually” tightly coupled components together in one modules.
  • the same is valid for the Query Module. Separate the query module based on the audience they serve the queries to. In some cases, even you command module needs to perform queries. In that case, consider putting the query component inside the module that needs it.
  • api module - I name this “core-api”, usually. While it’s nice to have a separate module to make it easy to share classes between modules, beware that the “contract” you have with recipients of your messages is the serialized form of the message, not the class itself.

Regarding the unit tests, mocking out RabbitMQ is probably pretty complex. The use of containers is now a commodity, so let’s also adapt our behavior to it. If you need RabbitMQ, just launch it in a container and use it. Mocking (and Stubbing, for that matter) are useful techniques when starting the real thing is too complex or expensive. With containers, that’s hardly the case…

Cheers,

Allard