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.
I can't see any synchronization in your test. Messaging is asynchronous by nature (meaning that your 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.
Related
TL/DR: The problem boils down to creating a custom Spring scope, injecting a prototype-like scoped bean into a singleton with proxyMode = ScopedProxyMode.TARGET_CLASS but still getting a singleton in the Java config version of the configuration (whereas it works fine with XML).
UPDATE: Problem solved, see answer.
I'm using jBehave to write BDD test scenarios for our Spring application. We recently thought that we need independence in executing test scenarios (meaning that test context has to be reset before each scenario) and found this article on the web that addresses exactly the issue we're dealing with.
The article advises creating a custom Spring Scenario scope, assigning it to the class that represents test context and injecting an AOP proxy instead of the context file.
I've coded everything in accordance with the article and it worked great, but the thing is we need it in terms of Java config, not XML, and when I converted all the changes to Java config, it stopped working - meaning the Map in StoryContext was not reset after each test scenario and contained values from the previous scenario.
My changes were as follows:
marked the ScenarioScope class with the #Component annotation:
#Component
public class ScenarioScope implements Scope {
private final ConcurrentMap<String, Object> cache = new ConcurrentHashMap<>();
#BeforeScenario
public void startScenario() {
cache.clear();
}
#Override
public Object get(String name, ObjectFactory<?> objectFactory) {
return cache.putIfAbsent(name, objectFactory.getObject());
}
#Override
public Object remove(String name) {
return cache.remove(name);
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
#Override
public String getConversationId() {
return "scenario scope";
}
}
created a Spring configuration class to add the new scope:
#Configuration
public class SpringConfiguration {
#Bean
public static CustomScopeConfigurer scopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("scenario", new ScenarioScope());
return configurer;
}
}
annotated the StoryContext class with the #Component and #Scope annotations:
#Component
#Scope(value = "scenario", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class StoryContext {
private Map<String, Object> storyContext = new HashMap<>();
public void put(String key, Object value) {
storyContext.put(key,value);
}
public <T> T get(String key, Class<T> tClass) {
return (T) storyContext.get(key);
}
#PostConstruct
public void clearContext() {
storyContext.clear();
}
}
To my knowledge, the code above is analogous to the XML configuration, which was as follows:
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation=" http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
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.xsd">
<context:annotation-config />
<context:component-scan base-package="foo"/>
<bean id="scenarioScope" class="foo.ScenarioScope"/>
<bean class="foo.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="scenario" value-ref="scenarioScope"/>
</map>
</property>
</bean>
<bean id="storyContext" class="foo.StoryContext" scope="scenario">
<aop:scoped-proxy/>
</bean>
</beans>
Can anyone please point me to why the Java config is not working as expected? I've spent some time researching stackoverflow but the majority of similar questions is solved by adding proxyMode = ScopedProxyMode.TARGET_CLASS to the #Scope annotation, which I did.
UPDATE: So I tried to gradually move from XML to Java config by commenting / decommenting corresponding lines in the files and figured out that the problem is in this part of the code:
<bean class="foo.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="scenario" value-ref="scenarioScope"/>
</map>
</property>
</bean>
When I replace it with
#Configuration
public class SpringConfiguration {
#Bean
public static CustomScopeConfigurer scopeConfigurer() {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("scenario", new ScenarioScope());
return configurer;
}
}
the StoryContext bean becomes a singleton. I tried doing it another way through registering a custom BeanFactoryPostProcessor and using the registerScope() method as described here, but it didn't work either.
I've managed to solve the problem, and the solution was trivial: the ScenarioScope instance in the SpringConfiguration class has to be managed by the Spring container rather than be created via the new() operator:
#Configuration
public class SpringConfiguration {
#Bean
public static CustomScopeConfigurer scopeConfigurer(ScenarioScope scenarioScope) {
CustomScopeConfigurer configurer = new CustomScopeConfigurer();
configurer.addScope("scenario", scenarioScope);
return configurer;
}
}
First of all, I know there are very similar questions (Camel producerTemplate is not injected in spring MVC and Initializing camel from Spring annotation config) but they don't help in my case.
I have a bean which sends messages with ProducerTemplate:
public class SimpleProducer {
#Produce(uri = "activemq:queue:simple")
private ProducerTemplate activeMqProducer;
public void send(String message) {
activeMqProducer.sendBody(message);
}
}
When I use annotation driven configuration like below, it trhows NPE from the send method (activeMqProducer is not injected):
#Configuration
public class AnnotationConfigApp {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AnnotationConfigApp.class);
SimpleProducer simpleProducer = context.getBean(SimpleProducer.class);
simpleProducer.send("Hello World!");
}
#Autowired
private ApplicationContext ctx;
#Bean
public SimpleProducer simpleProducer() {
return new SimpleProducer();
}
#Bean
public CamelContext camelContext() throws Exception {
CamelContext camelContext = new SpringCamelContext(ctx);
camelContext.start();
return camelContext;
}
}
while using equivalent (at least I believe so) XML configuration, it sucessfully sends a message to ActiveMQ:
<?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:spring="http://camel.apache.org/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://camel.apache.org/schema/spring http://camel.apache.org/schema/spring/camel-spring.xsd">
<bean id="simpleProducer" class="makasprzak.so.camel.producer.testing.SimpleProducer"/>
<spring:camelContext xmlns="http://camel.apache.org/schema/spring" id="simple.sender" />
</beans>
initialized like this:
public class XmlConfigApp {
public static void main(String[] args) {
ApplicationContext context = new GenericXmlApplicationContext("context.xml");
SimpleProducer simpleProducer = context.getBean(SimpleProducer.class);
simpleProducer.send("Hello World!");
}
}
I've been playing with CamelContext implementation a bit, tried DefaultCamelContext or some SpringCamelContextFactory - no luck.
The problematic code is available in GitHub
<properties>
<camel.version>2.15.2</camel.version>
<activemq.version>5.10.0</activemq.version>
<java.version>1.8</java.version>
</properties>
What did I miss in the annotation configuration?
You should extend org.apache.camel.spring.javaconfig.CamelConfiguration in AnnotationConfigApp class
I am trying to convert the following Spring task xml configuration to a purely code/annotation based version:
<task:executor id="xyz.executor"
pool-size="${xyz.job.executor.pool.size:1-40}"
queue-capacity="${xyz.job.executor.queue.capacity:0}"
rejection-policy="CALLER_RUNS"/>
<task:scheduler id="xyz.scheduler" pool size="${xyz.job.scheduler.pool.size:4}" />
<task:annotation-driven executor="xyz.executor" scheduler="xyz.scheduler" />
<bean id='xyzProcessor' class="xyz.queueing.QueueProcessor" />
<task:scheduled-tasks scheduler="xyz.scheduler" >
<task:scheduled ref="partitioner" method="createPartitions" cron="${xyz.job.partitioner.interval:0 0 3 * * *}" />
</task:scheduled-tasks>
Per the Spring spec, 28.4.1 (http://docs.spring.io/spring/docs/current/spring-framework-reference/html/scheduling.html), they say that to go from XML like this:
<task:annotation-driven executor="myExecutor" scheduler="myScheduler"/>
<task:executor id="myExecutor" pool-size="5"/>
<task:scheduler id="myScheduler" pool-size="10"/>
to code configuration is as simply as enabling either #EnableScheduling and/or #EnableAsync.
However, I don't see anywhere I can actually instantiate the scheduler. The javadoc for #EnableScheduling (http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/scheduling/annotation/EnableScheduling.html) shows how I can get plug in my own created Executor, though I'm not exactly sure what class it should be (I still want to be able to control the pool size, queue capacity, and rejection policy). It also shows how I can schedule my createPartitions method using the configureTasks override. However, I would like to be able to name my scheduler (so I can identify its threads) and control its pool size.
So, I wish to know these things:
1) What class can I use to set the executor fields that the XML has?
2) Is there a way to create a scheduler instance that I can control the name and pool size of?
Check out the types AsyncConfigurer, AsyncConfigurerSupport, and SchedulingConfigurer. They are helper types you can use to enhance your #Configuration class with async/scheduling configurations.
Across all of them, and the javadoc of #EnabledAsync, you'll find all the setup methods you need to setup your async/scheduling #Configuration class.
The example given equates
#Configuration
#EnableAsync
public class AppConfig implements AsyncConfigurer {
#Bean
public MyAsyncBean asyncBean() {
return new MyAsyncBean();
}
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(7);
executor.setMaxPoolSize(42);
executor.setQueueCapacity(11);
executor.setThreadNamePrefix("MyExecutor-");
executor.initialize();
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new MyAsyncUncaughtExceptionHandler();
}
}
with
<beans>
<task:annotation-driven executor="myExecutor" exception-handler="exceptionHandler"/>
<task:executor id="myExecutor" pool-size="7-42" queue-capacity="11"/>
<bean id="asyncBean" class="com.foo.MyAsyncBean"/>
<bean id="exceptionHandler" class="com.foo.MyAsyncUncaughtExceptionHandler"/>
</beans>
SchedulingConfigurer has a similar setup for task:scheduler.
If you want more fine-grained control you can additionally implement the SchedulingConfigurer and/or AsyncConfigurer interfaces.
As belows,
Please notice the pools also,
#Configuration
#EnableScheduling
public class CronConfig implements SchedulingConfigurer{
#Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
#Bean(destroyMethod="shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(10);
}
}
And for the Asyncs,
#Configuration
#EnableAsync
public class AsyncConfig implements AsyncConfigurer {
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.initialize();
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return new SimpleAsyncUncaughtExceptionHandler();
}
}
Note that #EnableAsync and #EnableScheduling has to be there for this to work.
I'm trying to set up a simple Spring AMQP scenario, but am getting the following error:
Could not resolve method parameter at index 0 in method:
public void handleMessage(HelloMessage),
with 1 error(s): [Error in object 'msg': codes []; arguments [];
default message [#Payload param is required]]
I don't understand the error message, I was under the impression I can use any POJO to send and receive a message, as per the documentation here.
It's a very simple setup:
main
public class Program {
private static ConfigurableApplicationContext applicationContext;
public static void main(String[] args) {
try {
startApp();
System.out.println("Running...");
System.in.read();
applicationContext.close();
System.out.println("Shutting down...");
}
catch (Throwable e) {
e.printStackTrace();
}
}
private static void startApp() {
applicationContext = new ClassPathXmlApplicationContext("/application-context.xml");
applicationContext.refresh();
MessageSender messageSender = applicationContext.getBean(MessageSender.class);
messageSender.sendMessage("hello", 1);
}
application-context.xml
<context:component-scan base-package="org.abiri.amqpTest" />
<rabbit:connection-factory id="connectionFactory"
host="localhost" port="5672"
username="guest" password="guest"/>
<rabbit:template id="amqpTemplate" connection-factory="connectionFactory"/>
<rabbit:admin connection-factory="connectionFactory"/>
<rabbit:queue name="hello"/>
<bean id="rabbitListenerContainerFactory"
class="org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="concurrentConsumers" value="3"/>
<property name="maxConcurrentConsumers" value="10"/>
</bean>
<rabbit:annotation-driven container-factory="rabbitListenerContainerFactory"/>
<rabbit:listener-container connection-factory="connectionFactory" />
MessageSender
#Service
public class MessageSender {
#Autowired
private AmqpTemplate amqpTemplate;
// Accessors...
public void sendMessage(String message, Integer sillyNumber) {
amqpTemplate.convertAndSend("hello",
new HelloMessage(message, sillyNumber));
}
}
MessageListener
#Component
public class MessageListener {
#RabbitListener(queues = "hello")
public void handleMessage(HelloMessage msg) {
out.println(format("Received message: %s with silly number: %d",
msg.getMessage(), msg.getSillyNumber()));
}
}
HelloMessage
public class HelloMessage {
private String message;
private Integer sillyNumber;
// Empty constructor, full constructor and accessors
}
I am able to verify that the message was indeed sent and is in the queue:
RabbitMQ default installation via homebrew.
Your issue is very simple!
To send any Java object over RabbitMQ with default SimpleMessageConverter you have to to mark your class as Serializable:
public class HelloMessage implements Serializable {
....
}
Of course, a listener application should have the same class in its CLASSPATH to have an ability to deserialize byte[] from payload (AMQP Message body) to the appropriate HelloMessage object.
I want to run the following method every specific time in spring mvc project it works fine and print first output
but it doesn't access the database so it doesn't display list
the method
public class ScheduleService {
#Autowired
private UserDetailService userDetailService;
public void performService() throws IOException {
System.out.println("first output");
List<UserDetail> list=userDetailService.getAll();
System.out.println(list);
}
config file
<!-- Spring's scheduling support -->
<task:scheduled-tasks scheduler="taskScheduler">
<task:scheduled ref="ScheduleService" method="performService" fixed-delay="2000"/>
</task:scheduled-tasks>
<!-- The bean that does the actual work -->
<bean id="ScheduleService" class="com.ctbllc.ctb.scheduling.ScheduleService" />
<!-- Defines a ThreadPoolTaskScheduler instance with configurable pool size. -->
<task:scheduler id="taskScheduler" pool-size="1"/>
try this (and remove bean definition from xml file):
#Component
public class ScheduleService {
#Autowired
private UserDetailService userDetailService;
#Scheduled(fixedDelay = 2000L) // in msec
public void performService() throws IOException {
System.out.println("first output");
List<UserDetail> list=userDetailService.getAll();
System.out.println(list);
}
}
Write an integration test for that specific service and see if the service method calls returns anything at all. Manually testing always leads to such problems. Start with tests and debug if necessary.