I'm experiencing following problem. I've got a spring boot test, where I inject and spy the mongoDbChannel bean. Then I try to start the normal workflow and verify if the method send is called on the bean.
#RunWith(SpringRunner.class)
#SpringBootTest(classes = {MongoAsBackupConfig.class},
properties = {},
webEnvironment = SpringBootTest.WebEnvironment.NONE)
public class MongoAsBackupConfigTest {
#SpyBean(name = "mongoDbChannel")
private QueueChannel mongoDbChannel;
#Autowired
private DirectChannel mongoDbWithFailoverChannel;
#DirtiesContext
#Test
public void shouldUseFallbackForFullQueue() throws InterruptedException {
IntStream.rangeClosed(1, BACKUP_QUEUE_CAPACITY + OVERFILLING_CLICK_COUNT).forEach(someNumber ->
mongoDbWithFailoverChannel.send(MessageBuilder.withPayload(createPayload(someNumber)).build()));
verify(mongoDbChannel, times(BACKUP_QUEUE_CAPACITY)).send(Mockito.any());
}
}
As a result, I get the error message that any doesn't match to the concrete parameter value. However normally any means any value of param. What went wrong here?
Argument(s) are different! Wanted:
mongoDbChannel.send(
<any>
);
-> at MongoAsBackupConfigTest.shouldUseFallbackForFullQueue(MongoAsBackupConfigTest.java:67)
Actual invocation has different arguments:
mongoDbChannel.send(
GenericMessage [payload=Click(...), headers={id=0eaa2317-b1b5-604d-65c5-78da521cd585, timestamp=1509085945379}],
10
);
-> at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:115)
EDITED:
I'm using java 8. And I tried to use any(GenericMessage.class), any(Message.class) but it was the same effect.
I assume you are using java 8 which means that when using Mockito.any(), the compiler will infer the type that has to be used based on the parameter type in the signature of send method.
That seems to be Message based on the method definition : send(Message<?> message)
What is actually passed is an instance of GenericMessage.
As I assume GenericMessage extends Message, then you can write your verify as follows:
verify(mongoDbChannel, times(BACKUP_QUEUE_CAPACITY))
.send(Mockito.any(GenericMessage.class));
Update
There also seems to be an overloaded method send(Message<?> message, long timeout). Maybe this version gets called instead of the single arg one..
Related
I have been generating Test Cases for some classes using a tool called Diff Blue. Now Diff Blue is an AI tool that can generate multiple Test Cases for some functions. I have a particular class for which I have generated Test Cases using Diff Blue but one Test Case has failed. My main goal is to get all of the Test Cases to pass. The Test Case I'm working on is for the below class with function getDashboardById.
#RequiredArgsConstructor
#Transactional
#Service
#Slf4j
public class DashboardServiceImpl implements DashboardService {
private final DashboardRepository dashboardRepository;
private final PlatformDataExtractHistoryService platformDataExtractHistoryService;
private final ShopifyService shopifyService;
private final ModelMapper modelMapper;
/**
* Get Dashboard by its id
*
* #param id dashboard id
* #return DashboardGetDto
*/
#Override
public DashboardGetDto getDashboardById(String id) {
Dashboard dashboard = dashboardRepository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException(GlobalErrorUtils.DASHBOARD_NOT_FOUND_404));
log.info("Get dashboard details by id: {}", id);
// convert entity to DTO
return modelMapper.map(dashboard, DashboardGetDto.class);
}
Diffblue has generated 3 Test Cases for the function getDashboardById. Two of the Test Cases passed while one failed. My goal is to get it to pass too. The below code is the code of the failed test case.
void testGetDashboardById3() {
DashboardGetDto dashboardGetDto = new DashboardGetDto();
when(dashboardRepository.findById((String) any())).thenReturn(Optional.empty());
when(modelMapper.map((Object) any(), any())).thenReturn("Map");
when(modelMapper.map((Object) any(), (Class<DashboardGetDto>) any())).thenReturn(dashboardGetDto);
assertThrows(ResourceNotFoundException.class, () -> dashboardServiceImpl.getDashboardById("42"));
verify(dashboardRepository).findById((String) any());
verify(modelMapper).map((Object) any(), (Class<Object>) any());
}
The problem I'm experiencing is that I'm getting a Class Cast Exception.
java.lang.ClassCastException: class java.lang.String cannot be cast to class com.unicorn.dashboard.dto.dashboard.DashboardGetDto (java.lang.String is in module java.base of loader 'bootstrap'; com.unicorn.dashboard.dto.dashboard.DashboardGetDto is in unnamed module of loader 'app')
at com.unicorn.dashboard.serviceImpl.DashboardServiceImplTest.testGetDashboardById3(DashboardServiceImplTest.java:127)
Now I do understand that this occurs when you try to cast a class of one type to another. But what I'm struggling to understand is where the error is being generated and how do I solve it since this is a Test Case. The Error points at line 127 which is in the above Test Function testGetDashboardById3. For reference I'll give the line below
when(modelMapper.map((Object) any(), (Class<DashboardGetDto>) any())).thenReturn(dashboardGetDto);
Now what I don't understand is that where exactly in the above line is the Cast occurring and what must be changed in order to get the Test Case working?
Edit
I have edited my code as per the answers and displayed it below. However there is an error within the code. What necessary changes must be done to fix the error?
When I hover over the highlighted area I get this message
when(modelMapper.map((Object) any(), any())).thenReturn("Map"); matches any second argument. You want to mock method <D> D map(Object source, Class<D> destinationType) which you would call as map(input, String.class) and map(input, DashboardGetDto.class) in your production code. Therefore, you want to set up this method as:
when(modelMapper.map(any(), eq(String.class)))
.thenReturn("Map");
when(modelMapper.map(any(), eq(DashboardGetDto.class)))
.thenReturn(dashboardGetDto);
to select the correct overloads. Your test was matching calls such as map(input, new DashboardGetDto()), which returns void.
Note that ArgumentMatchers#any() is already generic, so you don't have to cast the return value. You can either use ArgumentMatchers.<Object>any() or any(Object.class).
Important to note is that the Java runtime cannot distinguish between Class<X> and Class<Y> (or List<A> and List<B>). For the JVM, both are of type Class (or List, respectively). Your first when call was already matching all calls to your mock.
I have several Aspects coded in my application. All others works except for the following.
Service Interface
package com.enbiso.proj.estudo.system.service;
...
public interface MessageService {
...
Message reply(Message message);
Message send(Message message);
...
}
Service Implementation
package com.enbiso.proj.estudo.system.service.impl;
....
#Service("messageService")
public class MessageServiceImpl implements MessageService {
...
#Override
public Message reply(Message message) {
...
return this.send(message);
}
#Override
public Message send(Message message) {
...
}
}
Aspect
#Aspect
#Component
public class NewMessageAspect {
...
#AfterReturning(value = "execution(* com.enbiso.proj.estudo.system.service.impl.MessageServiceImpl.send(..))",
returning = "message")
public void perform(Message message){
...
}
}
When I try to execute the send method the debug point is not getting hit in the aspect perform.
UPDATE
I did some investigations and found that this doesn't work, when the send method is invoked from the reply method as below
#Autowire MessageService messageService;
...
messageService.reply(message);
But if I call the method messageService.send(message) it works fine. But as reply method is calling send method internally, shouldn't it also invoke the aspect?
I have no idea what i have done wrong. Please help me.
Thank you jst for clearing the things up. Just for the information purposes for the future developer in SO, I'm posting the full answer to this question
Lets assume that there is a bean from SimplePojo
public class SimplePojo implements Pojo {
public void foo() {
this.bar();
}
public void bar() {
...
}
}
When we call the method foo(), it reinvokes the method bar() inside it. Even thought the method foo() is invoked from the AOP Proxy, the internal invocation of the bar() is not covered by the AOP Proxy.
So eventually this makes, if there are any advices attached to the method bar() to not get invoked
Solution
Use AopContext.currentProxy() to call the method. Unfortunately this couples the logic with AOP.
public void foo() {
((Pojo) AopContext.currentProxy()).bar();
}
Reference:
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies
You are running into a limitation of Spring AOP on self-invocation. You basically can get around it by using AopContext.currentProxy(), refactor code into different beans, or use full ApsectJ weaving.
See explanation here and workaround(s).
http://docs.spring.io/spring/docs/current/spring-framework-reference/html/aop.html#aop-understanding-aop-proxies
I guess the problem is the #args condition.
Spring documentation states the following:
#args - limits matching to join points (the execution of methods when using Spring AOP) where the runtime type of the actual arguments passed have annotations of the given type(s)
Therefore the parameter of #args has to be a type expression. So the correct pointcut expression is
#AfterReturning(value = "execution(* com.enbiso.proj.estudo.system.service.impl.MessageServiceImpl.send(..)) && args(com.enbiso.proj.estudo.system.service.impl.Message")
or simply
#AfterReturning(value = "execution(* com.enbiso.proj.estudo.system.service.impl.MessageServiceImpl.send(com.enbiso.proj.estudo.system.service.impl.Message))")
Please adjust the package of Message if it doesn't fit.
The exact usage is like this:
#Slf4j
public class Client<E, Key> {
#Getter #NonNull private final UpdateListener<E, Key> updateListener;
#NonNull private final SubscriptionFactory subscriptionFactory;
#NonNull private final Map<Key, Instant> updatedRegistry = new ConcurrentHashMap<>();
public Client(UpdateListener<E, Key> updateListener,
SubscriptionFactory subscriptionFactory) {
this.updateListener = updateListener;
this.subscriptionFactory = subscriptionFactory;
this.subscriptionFactory.registerSnapshotClient(updateListener);
log.info("Created new snapshot client for entity key [{}], update type [{}] and component qualifier [{}]",
updateListener.getEntityKey(),
updateListener.getOptionalChangeType(),
updateListener.getComponentQualifier());
}
#RabbitListener(queues = {"#{#queueNameCreator.createUpdateQueueName(snapshotClient.getUpdateListener())}",
"#{#queueNameCreator.createSnapshotQueueName(snapshotClient.getUpdateListener())}"})
public void handleMessage(Message<E> rawUpdate, #Header("last_updated") Instant newUpdatedTime) {
...//more code
}
}
Each 'Client' instance has its own bean id to not clash with each other.
How can I call get the exact updateListener of this object using SpEl?
Update
After using programattical approach and registering method I get the following exception:
Apr 28, 2015 3:22:47 PM org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler handleError
WARNING: Execution of Rabbit message listener failed.
org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException: Listener method 'public void com.everymatrix.om2020.messaging.model.SnapshotClient.handleMessage(org.springframework.messaging.Message<E>,java.time.Instant)' threw exception
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.invokeHandler(MessagingMessageListenerAdapter.java:126)
at org.springframework.amqp.rabbit.listener.adapter.MessagingMessageListenerAdapter.onMessage(MessagingMessageListenerAdapter.java:93)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.doInvokeListener(AbstractMessageListenerContainer.java:756)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.invokeListener(AbstractMessageListenerContainer.java:679)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$001(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$1.invokeListener(SimpleMessageListenerContainer.java:167)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.invokeListener(SimpleMessageListenerContainer.java:1241)
at org.springframework.amqp.rabbit.listener.AbstractMessageListenerContainer.executeListener(AbstractMessageListenerContainer.java:660)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.doReceiveAndExecute(SimpleMessageListenerContainer.java:1005)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.receiveAndExecute(SimpleMessageListenerContainer.java:989)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer.access$700(SimpleMessageListenerContainer.java:82)
at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1103)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalStateException: No suitable resolver for argument [0] [type=org.springframework.messaging.Message]
Update
Done, you need to do the following to achieve the desired behaviour.
#Configuration
#EnableRabbit
public static class OmbeRabbitListenerConfigurer implements RabbitListenerConfigurer {
#Autowired ApplicationContext applicationContext;
#Autowired SnapshotClientQueueNamesCreator snapshotClientQueueNamesCreator;
#Autowired RabbitListenerContainerFactory rabbitListenerContainerFactory;
#Autowired MessageConverter messageConverter;
#Override
public void configureRabbitListeners(RabbitListenerEndpointRegistrar registrar) {
final Collection<SnapshotClient> snapshotClients = applicationContext.getBeansOfType(SnapshotClient.class).values();
System.out.println(snapshotClients);
snapshotClients.stream().forEach(bean -> {
final String snapshotQueueName = snapshotClientQueueNamesCreator.createSnapshotQueueName(bean.getUpdateListener());
final String updateQueueName = snapshotClientQueueNamesCreator.createUpdateQueueName(bean.getUpdateListener());
Method method = Stream.of(bean.getClass().getMethods()).filter(x -> x.getName().equals("handleMessage")).findAny().get();
MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
final DefaultMessageHandlerMethodFactory messageHandlerMethodFactory = new DefaultMessageHandlerMethodFactory();
messageHandlerMethodFactory.afterPropertiesSet();
endpoint.setMessageHandlerMethodFactory(messageHandlerMethodFactory);
endpoint.setBean(bean);
endpoint.setMethod(method);
endpoint.setId(snapshotQueueName + ":" + updateQueueName + UUID.randomUUID());
endpoint.setQueueNames(snapshotQueueName, updateQueueName);
endpoint.setExclusive(false);
registrar.registerEndpoint(endpoint, rabbitListenerContainerFactory);
});
}
}
Your question is not clear - you seem to be mixing runtime and initialization time concepts.
For example, "#{#queueNameCreator.createUpdateQueueName(e.c.doSomething())}" is evaluated once during initialization - it's not clear from this expression what e is, or where it comes from.
But, you seem to be passing in an E in the payload of message: Message<E> rawUpdate. This message came from the queue and therefore can't influence the queue name.
Perhaps if you can explain what you are trying to do rather than how you have attempted to do it, I can update this "answer" with possible solutions.
EDIT:
If you mean you want to reference some field in the current (listener) bean in your SpEL then it can't be done directly.
EDIT2:
I can't think of any way to get a reference to the current bean in the SpEL expression - it has to be a constant; that's just the way annotations work in Java; they are tied to the class, not the instance.
I think to do what you want, you would need to revert to using programmatic endpoint registration. However, you'd need to wire in a MethodRabbitListenerEndpoint (rather than the SimpleRabbitListenerEndpoint) to get the benefits of the annotation you are looking for (#Header etc).
We don't really cover it in the documentation; it's a little advanced, but essentially, you need to inject the bean and Method (for the listener), and a DefaultMessageHandlerMethodFactory.
I have just started working with Mockito and am having a problem with one of the tests failing, yet the actual code works correctly in a live environment. The controller being tested is:
#Controller
#RequestMapping("/notes")
public class NotesController {
private NoteRepository noteRepository;
private MyUserRepository userRepository;
#RequestMapping(value = "/add", method = POST)
public String postNote(#Valid Note note, BindingResult errors, Principal principal){
String username = principal.getName();
MyUser user = userRepository.findUserByUsername(username);
note.setMyUser(user);
note.setTime(new Date());
noteRepository.save(note);
return "redirect:/notes"; }
}
The test is here:
#Test
public void testShouldAddValidNote() throws Exception {
MyUser testing = new MyUser();
Note note = new Note();
NoteRepository noteRepository = mock(NoteRepository.class);
when(noteRepository.save(note)).thenReturn(note);
MyUserRepository userRepository = mock(MyUserRepository.class);
when(userRepository.findUserByUsername("testing")).thenReturn(testing);
Principal mockPrincipal = mock(Principal.class);
when(mockPrincipal.getName()).thenReturn("testing");
NoteController controller = new NoteController(noteRepository);
controller.setMyUserRepository(userRepository);
MockMvc mockMvc = standaloneSetup(controller).build();
mockMvc.perform(post("/notes/add")
.requestAttr("note", note)
.principal(mockPrincipal))
.andExpect(view().name("redirect:/notes"));
verify(noteRepository,times(1)).save(note);
verify(note,times(1)).setMyUser(testing);
verify(note,times(1)).setTime((Date)anyObject());
verify(userRepository,times(1)).findUserByUsername("testing");
verify(mockPrincipal,times(1)).getName();
}
The first verify test fails, with the message:
Argument(s) are different! Wanted:
noteRepository.save(projectName.Note#5ae9);
Actual invocation has different arguments:
noteRepository.save(projectName.Note#c079ae45
Clearly the Note object passed in to the method has changed, but I thought that using .requestAttr("note", note) would pass in the reference and the same object should therefore be in the method (and later returned). Like I said, it works perfectly in the live web Container, so what am I getting wrong with the Mockito test please?
This is just a wild guess could the issue this code faces comes from MockMvc / MockMvcRequestBuilders where the Note is somehow serialized / deserialized between the request configuration and the actual request ?
Note that note is a real object so calling verify(note).... won't work.
Anyway I suggest the use of the combination of a mockito captor and AssertJ in this case :
// if field instantiation if using the mockito runner / rule or MockitoAnnotations.initMocks(this)
#Captor ArgumentCaptor<Note> noteCaptor;
// if created in the test method
ArgumentCaptor<Note> noteCaptor = ArgumentCaptor.forClass(Note.class);
// ...
verify(noteRepository,times(1)).save(noteCaptor.capture());
assertThat(noteCaptor.getValue().geMyUser()).isEqualTo(testing);
assertThat(noteCaptor.getValue().geTime()).isCloseTo(someDate);
Note I'm on a phone
I need to test a service class, but when I try to mock the dao class, it doesn't get triggered, thus not able to use ThenReturn().
I think that the problem is because I use an interface for my Dao and #Autowired in the service class (Spring MVC 3.1):
The interface:
public interface TestDao {
int createObject(Test test) throws NamingException;
}
The implementation:
#Repository
public class TestDaoImpl implements TestDao {
#Override
public int createObject(Test test) {
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new InsertNewTest(test), keyHolder);
return ((java.math.BigDecimal)keyHolder.getKey()).intValue();
}
}
The service:
public class RegTest {
#Autowired
TestDao testDao;
public int regTest(int .....) {
.
.
int cabotageId = testDao.createObject(test);
}
}
In the test I have:
#RunWith(MockitoJUnitRunner.class)
public class TestRegService {
#InjectMocks
private RegTest regTest = new RegTest();
#Mock
TestDao testDao;
#Test()
public void test() {
.
when(testDao.createObject(null)).thenReturn(100);
.
}
testDao.createObject(null) returns 0 (due to being mock'ed) and not 100 as I is trying to achieve.
Can anybody help, please?
Problem solved!
It was the passing test-object to createObject() that did not match. Using
testDao.createObject(any(Test.class))
did the trick!
If your test is actually passing a value to createObject, then when(testDao.createObject(null)... never gets matched. Rather than matching on null, you could match any instance of Test with testDao.createObject(any(Test.class))...
Also when you tried later to supply new Test() as the argument to match, it will literally try to match on that exact instance of Test, but presumably your real code is new-ing up a different one. So the use of Matchers.any(Test.class) as the parameter to match is the way to go.
Mockito injection mechanism don't know about Spring #Autowired or CDI #Inject annotations. It just tries to find the best candidate given the type and the name of the mock, and it can lookup private fields too. See the javadoc of #InjectMocks : http://docs.mockito.googlecode.com/hg/1.9.0/org/mockito/InjectMocks.html
The semantic you are using is correct, though if you are experiencing issues, I would rather look for incorrect interactions or incorrect arguments.
Are you sure the test variable in regTest.regTest(int...) is really null when passed to testDao.createObject(test) ?
I don't know if this is a typo in the example, but you have RegTest.regTest() calling createTest() rather than createObject(). Otherwise, I don't think #Autowired has anything to do with it, since your test itself is not running in a container with Spring management. If it is not a typo, and createTest is in fact a real and different method from createObject, then the default behaviour of a mocked object in Mockito is to return the appropriately-typed zero for numeric return types.
I think that you're right about the autowire not getting called. You could inject the dao yourself using the setTestDao() call instead. Mockito also supports spy which allows you to trace the objects code and just replace functions instead.