Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Testing RabbitMQ with Spring and Mockito

Attempting to have Spring JUnit runner run test with RabbitTemplate and the listener injected with a Mockito stubbed service class. Trying to verify interaction with the Mock. With the examples I've seen I thought this would be possible. RabbitMQ is working. When logging into the dashboard, I can see the messages there. Am able to consume messages also with standalone console application.

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath:/spring/servlet-context.xml", "classpath:/spring/root-context.xml", "classpath:rabbitlistener-context-test.xml"})
public class ReceiveOrderQueueListenerTest {

    @Mock
    private ReceiveOrderRepository mockRepos;

    @Autowired
    RabbitTemplate rabbitTemplate;

    @Autowired
    SimpleMessageListenerContainer  listenerContainer;

    @InjectMocks
    @Autowired
    ReceiveOrderQueueListener receiveOrderQueueListener;


    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
    }

    @Test
    public void testAbleToReceiveMessage() {
        RequestForService rfs = new RequestForService();
        rfs.setClaimNumber("a claim");
        rabbitTemplate.convertAndSend("some.queue", rfs);
        verify(mockRepos).save(new OrderRequest());
    }

} 

then the rabbit-listener config

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:rabbit="http://www.springframework.org/schema/rabbit"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd 
          http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd">

     <rabbit:connection-factory id="rabbitConnectionFactory" host="XXXXXXXXX.com"   username="test" password="test" /> 
 <!--    <rabbit:connection-factory id="rabbitConnectionFactory" host="localhost" username="guest" password="guest" /> -->

    <rabbit:admin connection-factory="rabbitConnectionFactory" auto-startup="true"  />

    <rabbit:template id="tutorialTemplate" connection-factory="rabbitConnectionFactory"  exchange="TUTORIAL-EXCHANGE"/>

    <rabbit:queue name="some.queue"  id="some.queue"></rabbit:queue>

    <bean id="receiveOrderListener" class="XXXXXXXXXX.connect.message.ReceiveOrderQueueListenerImpl"></bean>


    <rabbit:topic-exchange id="myExchange" name="TUTORIAL-EXCHANGE">
        <rabbit:bindings>
            <rabbit:binding queue="some.queue"   pattern="some.queue"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:topic-exchange>

    <rabbit:listener-container connection-factory="rabbitConnectionFactory">
    <!--    <rabbit:listener queues="some.queue" ref="receiveOrderListener" method="handleMessage"/> -->
        <rabbit:listener queues="some.queue" ref="receiveOrderListener" />
    </rabbit:listener-container>
</beans>

Tried injecting in the MessgeListenerAdaptor as well thinking the test needed that to wire the listener correct as well. With the adaptor injected in, I am able to verify the delegate gets injected in and with the stub. Test fails on no zero interactions with mock. I can log into rabbitmq and the messages are there. The injected listener object is not consuming messages from the queue during the run of this test.

Almost forgot, the said listener. Tried default signature and tried the designate method.

public class ReceiveOrderQueueListenerImpl implements ReceiveOrderQueueListener {

    @Autowired
    ReceiveOrderRepository receiveOrderRepository;

    @Override
    public void handleMessage(RequestForService rfs) {
        System.out.println("receive a message");
        receiveOrderRepository.save(new OrderRequest());
    }

    public void onMessage(Message message) {
        receiveOrderRepository.save(new OrderRequest());
    }

}

Any suggestions would be helpful and I appreciate your help ahead of time.

like image 616
Michael Sampson Avatar asked Jun 07 '15 19:06

Michael Sampson


People also ask

Can we use JUnit and Mockito together?

Using JUnit Jupiter's MockitoExtension xml file. This single dependency replaces both the mockito core and the junit dependencies defined above. We then add @ExtendWith(MockitoExtension. class) to the test class and annotate fields that we want to mock with the @Mock annotation.

How is Mockito used with spring?

Summary. Mocking in unit testing is extensively used in Enterprise Application Development with Spring. By using Mockito, you can replace the @Autowired components in the class you want to test with mock objects. You will be unit testing controllers by injecting mock services.

How do you test RabbitMQ listener?

In order to test that our Listener is capable of consuming the messages, first we must send a test message. . . . @Autowired private lateinit var rabbitTemplate: RabbitTemplate @Autowired private lateinit var rabbitAdmin: RabbitAdmin @Before fun setUp() { rabbitAdmin.


1 Answers

I can't see any synchronization in your test. Messaging is asynchronous by nature (meaning that tour test can finish before the message arrives).

Try using a Latch concept, which blocks until the message arrives or until the timeout expires.

First you need a test listener bean for your queue:

@Bean
@lombok.RequiredArgsContructor
@lombok.Setter
public class TestListener {

    private final ReceiveOrderQueueListener realListener;
    private CountDownLatch latch;

    public void onMessage(Message message) {
        realListener.onMessage(message);
        latch.countDown();
    }
}

Make sure to configure it to be used in place of your original listener in the test context.

Then in your test you can set the latch:

public class ReceiveOrderQueueListenerTest {

    @Autowired
    private TestListener testListener;

    @Test
    public void testAbleToReceiveMessage() {
        RequestForService rfs = new RequestForService();
        rfs.setClaimNumber("a claim");

        // Set latch for 1 message.
        CountDownLatch latch = new CountDownLatch(1);  
        testListener.setLatch(latch);

        rabbitTemplate.convertAndSend("some.queue", rfs);

        // Wait max 5 seconds, then do assertions.
        latch.await(5, TimeUnit.SECONDS);
        verify(mockRepos).save(new OrderRequest());
    }
}

Note that there are better ways how to use latches (e.g. channel interceptors or LatchCountDownAndCallRealMethodAnswer with Mockito), but this is the basic idea. See Spring AMQP testing reference for more info.

like image 81
stuchl4n3k Avatar answered Oct 07 '22 06:10

stuchl4n3k