Spring mockito bean still does nothing, despite doAnswer setting - spring

I'm trying to stub an injected dependency upon its void method:
#Autowired
private FeedbackService service;
#MockBean
private MailSender sender;
#Test
public void testMonitor() {
// mocking MailSender#sendMessage
Mockito.doAnswer(invocation -> {
log.info("SUBJECT: {}", invocation.getArgument(0, String.class));
log.info("CONTENT: {}", invocation.getArgument(1, String.class));
for (String dest : (String[]) invocation.getArgument(2)) {
log.info("DEST: {}", dest);
}
return null; // Void anyway
}).when(sender).sendMessage(anyString(), anyString(), any(String[].class)); //FIXME still doNothing
// invoking the service which calls MailSender#sendMessage
service.monitor();
}
But logging and debugging shows that no interception occurs at runtime.
Does anybody know what I'm doing wrong, please ?
The casting of string arrays seems to be trickier than I thought. This works as an interceptor:
.sendMessage(anyString(), anyString(), any());
But the third argument arrives as a String instead of a string array.
NB: that third parameter is a vararg but #anyVararg won't work either.

Ok, I finally figured out that even if the compiler would allow varargs to be treated as arrays, the invocation arguments would still consider arguments as separated entities.
#Test
public void testMonitor() {
// mocking MailSender#sendMessage
Mockito.doAnswer(invocation -> {
log.info("SUBJECT: {}", invocation.getArgument(0, String.class));
log.info("CONTENT: {}", invocation.getArgument(1, String.class));
for (int i = 2; i < invocation.getArguments().length; i++) {
log.info("DEST: {}", (String) invocation.getArgument(i));
}
return null; // Void
}).when(sender)
.sendMessage(anyString(), anyString(), (String[]) any());
// invoking the service, depending on the above stub
service.monitor();
}

Related

Mockito, how to mock call by reference method on same class

Why I can not mock callRefMethod method (call method by reference) on below code? The problem is real method of callRefMethod always being called.
public class ManageUserService {
public void callRefMethod(List<String> lsStr, boolean flag){
if (flag){
lsStr.add("one");
lsStr.add("two");
}
}
public void methodA(){
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
for(String str : lsStr){
System.out.println(str);
}
}
}
Unit tests:
public class ManageUserServiceTest {
#InjectMocks
private ManageUserService manageUserService;
private AutoCloseable closeable;
#BeforeEach
public void init() {
closeable = MockitoAnnotations.openMocks(this);
}
#AfterEach
void closeService() throws Exception {
closeable.close();
}
#Test
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
ManageUserService manageUserServiceA = new ManageUserService();
ManageUserService userSpy = spy(manageUserServiceA);
doNothing().when(userSpy).callRefMethod(lsData, true);
userSpy.methodA();
verify(userSpy).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}
}
The result :
zero
one
two
The problem is the difference between the list you're creating in the test method, which is used to match the expected parameters when "doing nothing":
List<String> lsData = new ArrayList<>();
lsData.add("start");
...
doNothing().when(userSpy).callRefMethod(lsData, true);
and the list created in the tested method, passed to the spy object:
List<String> lsStr = new ArrayList<>();
lsStr.add("zero");
this.callRefMethod(lsStr, true);
You're telling Mockito to doNothing if the list is: ["start"], but such list is never passed to the callRefMethod. ["zero"] is passed there, which does not match the expected params, so actual method is called.
Mockito uses equals to compare the actual argument with an expected parameter value - see: the documentation. To work around that ArgumentMatchers can be used.
You can either fix the value added to the list in the test or match the expected parameter in a less strict way (e.g. using anyList() matcher).
ok i did it by using : where manageUserServiceOne is spy of ManageUserService class
void methodATest(){
List<String> lsData = new ArrayList<>();
lsData.add("start");
doAnswer((invocation) -> {
System.out.println(invocation.getArgument(0).toString());
List<String> lsModify = invocation.getArgument(0);
lsModify.add("mockA");
lsModify.add("mockB");
return null;
}).when(manageUserServiceOne).callRefMethod(anyList(), anyBoolean());
manageUserServiceOne.methodA();
verify(manageUserServiceOne).callRefMethod(ArgumentMatchers.any(ArrayList.class), ArgumentMatchers.any(Boolean.class));
}

How to accept http requests after shutdown signal in Quarkus?

I tried this:
void onShutdown(#Observes final ShutdownEvent event) throws InterruptedException {
log.infof("ShutdownEvent received, waiting for %s seconds before shutting down", shutdownWaitSeconds);
TimeUnit.SECONDS.sleep(shutdownWaitSeconds);
log.info("Continue shutting down");
}
But after receiving ShutdownEvent Quarkus already responds with 503 to http requests. Looks like this could be done with ShutdownListener in preShutdown method. I have implemented this listener but it does not get called yet. How do I register ShutdownListener?
Use case here is OpenShift sending requests to terminating pod.
Option 1: Create Quarkus extension
Instructions are here. ShutdownController is my own class implementing ShutdownListener where I have a sleep in preShutdown method.
class ShutdownControllerProcessor {
#BuildStep
FeatureBuildItem feature() {
return new FeatureBuildItem("shutdown-controller");
}
#BuildStep
ShutdownListenerBuildItem shutdownListener() {
// Called at build time. Default constructor will be called at runtime.
// Getting MethodNotFoundException when calling default constructor here.
return new ShutdownListenerBuildItem(new ShutdownController(10));
}
}
Option 2: Modify ShutdownRecorder private static final field
New shutdown listener can be added using reflection. This is a bit ugly solution.
registerIfNeeded() need to be called after Quarkus startup, for example with timer 1 second after #PostConstruct.
#ApplicationScoped
public class ListenerRegisterer {
public void registerIfNeeded() {
try {
tryToRegister();
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
private void tryToRegister() throws NoSuchFieldException, IllegalAccessException {
final var field = ShutdownRecorder.class.getDeclaredField("shutdownListeners");
field.setAccessible(true);
final var listeners = (List<ShutdownListener>) field.get(null);
if (listeners != null && !listeners.toString().contains("ShutdownController")) {
listeners.add(new ShutdownController(10));
setFinalStatic(field, listeners);
}
}
private static void setFinalStatic(final Field field, final Object newValue) throws NoSuchFieldException, IllegalAccessException {
field.setAccessible(true);
final var modifiersField = Field.class.getDeclaredField("modifiers");
modifiersField.setAccessible(true);
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, newValue);
}
}

Errors: UnfinishedStubbing

I am writing Junit test case and I want to mock KafkaTemplate method kafkaTemplate.send(TOPIC_NAME, "someData");. In my project, I am using spring boot and Kafka.
Below is the StudentRecords class. I am using mockito for mocking the dependencies.
#Component
public class StudentRecords {
#Autowired
private KafkaTemplate<String, String> kafkaTemplate;
#Value("${topicNameForStudents}")
private String TOPIC_NAME;
public String sendStudentData(StudentDTO studentDTO) {
String studentStr = null;
try {
if(null == studentDTO) {
throw new StudentException("studentDTO Object cant be null");
}
if(studentDTO.getId() == null) {
throw new StudentException("Id cant be empty");
}
ObjectMapper mapper = new ObjectMapper();
studentStr = mapper.writeValueAsString(srvgExecution);
kafkaTemplate.send(TOPIC_NAME, studentStr);
return "SUCCESS";
} catch (JsonProcessingException e) {
e.printStackTrace();
return "ERROR";
}
}
}
And test class is as follows:
#ExtendWith({ SpringExtension.class, MockitoExtension.class })
class StudentRecordsTest {
#InjectMocks
StudentRecords studentRec;
#Mock
private KafkaTemplate<String, String> kafkaTemplate;
#Test
void testSendStudentData() {
StudentDTO studentDTO = new StudentDTO();
studentDTO.setId(1);
studentDTO.setName("ABC");
studentDTO.setAddress("Some Address");
Mockito.when(kafkaTemplate.send(Mockito.anyString(), Mockito.anyString()));
studentRec.sendStudentData(studentDTO);
}
}
And I getting the following error
[ERROR] Errors:
[ERROR] studentRec.testSendStudentData: ยป UnfinishedStubbing
It is happening at line studentRec.sendStudentData(studentDTO);
How I can resolve/write the junit for this?
#Test
void testSendStudentData() {
StudentDTO studentDTO = new StudentDTO();
studentDTO.setId(1);
studentDTO.setName("ABC");
studentDTO.setAddress("Some Address");
Mockito.when(kafkaTemplate.send(Mockito.anyString(), Mockito.anyString()));
studentRec.sendStudentData(studentDTO);
Mockito.verify(kafkaTemplate).send(Mockito.anyString(), Mockito.anyString());
}
after updating the junit to above one, ended up with below error at this statement Mockito.verify(kafkaTemplate).send(Mockito.anyString(), Mockito.anyString());
Argument(s) are different! Wanted:
kafkaTemplate.send(
<any string>,
<any string>
);
Your mock statement is incomplete.
Mockito.when(kafkaTemplate.send(Mockito.anyString(), Mockito.anyString()));
KafkaTemplate's send method returns a ListenableFuture object and hence you need to have your mock return it.
I understand, you are not really using the returned value as part of your code.
In that case you may simply return null as below.
Mockito.when(kafkaTemplate.send(Mockito.anyString(), Mockito.anyString())).thenReturn(null);
Although, I would suggest you should ideally check for return value for better error handling, but that can be a different story altogether.
In case you do plan to handle error by checking the return value, your above mock statement can be written to return both success and fail cases.
You may check below thread for more details on how to set the correct mock expectations for KafkaTemplate.
How to mock result from KafkaTemplate

Spring #Async with CompletableFuture

I have a doubt about this code:
#Async
public CompletableFuture<String> doFoo() {
CompletableFuture<String> fooFuture = new CompletableFuture<>();
try {
String fooResult = longOp();
fooFuture.complete(fooResult);
} catch (Exception e) {
fooFuture.completeExceptionally(e);
}
return fooFuture;
}
The question is: does doFoo return fooFuture only after longOp has finished (either correctly or exceptionally) and is therefore returning already completed futures or is Spring doing some magic and returning before executing the body? If the code is blocking on longOp(), how would you express that the computation is being fed to an executor?
Perhaps this? Any other way?
#Async
public CompletableFuture<String> doFoo() {
CompletableFuture<String> completableFuture = new CompletableFuture<>();
CompletableFuture.runAsync(() -> {
try {
String fooResult = longOp();
completableFuture.complete(fooResult);
} catch (Exception e) {
completableFuture.completeExceptionally(e);
}
});
return completableFuture;
}
Spring actually does all of the work behind the covers so you don't have to create the CompletableFuture yourself.
Basically, adding the #Async annotation is as if you called your original method (without the annotation) like:
CompletableFuture<User> future = CompletableFuture.runAsync(() -> doFoo());
As for your second question, in order to feed it to an executor, you can specify the exectutor bean name in the value of the #Async annotation, like so:
#Async("myExecutor")
public CompletableFuture<User> findUser(String usernameString) throws InterruptedException {
User fooResult = longOp(usernameString);
return CompletableFuture.completedFuture(fooResult);
}
The above would basically be the following as if you called your original method, like:
CompletableFuture<User> future = CompletableFuture.runAsync(() -> doFoo(), myExecutor);
And all of your exceptionally logic you would do with the returned CompletableFuture from that method.

Looking for a solution to extend Spring MVC with another Component/Annotation

Suppose I have a Website that is used in normal mode (browser) and in some other mode, like a MobileView mode (inside a mobile app). For each Controller I create, there might be correspondent controller for MobileView, processing the same url.
The easiest solution is to create ifs in all the Controllers that have MobileView logic. Another solution would be to use a correspondent url for MobileView (similar to the normal url) and two separate Controllers (possible where one extends from another; or use some other way to recycle common code)
But, a more elegant solution would be to have some extra annotations, like #SupportsMobileView (to mark a controller, and tell the app that this will have a correspondent MobileView Controller) and #MobileViewController (to mark a second controller, and tell the app that this controller needs to run immediately after the initial controller marked with #SupportsMobileView). The link between a normal controller and a MobileView controller would be through the url they process (defined with #RequestMapping).
Is it possible to extend Spring MVC (A)? Where to inject new annotation scanners (B) and annotation handlers / component handlers (C)? How should the MobileView controller be executed (D) (right now I am thinking that it could be executed through AOP, where the new handler of my new controller type programatically creates a Join-Point on the corresponding normal controller)
Note that I did not mention how this MobileView mode is triggered and detected. Let's just say that there a Session boolean variable (flag) for that.
Critics on any points (A), (B), (C) or (D) are welcomed, as well as technical hints and alternative solution to any point or the whole solution.
HandlerInterceptor can be used to intercept the RequestMapping handling. This is a simple example how to configure and implement one.
You can check for your session variable and will have a bunch of methods that will allow you to do custom processing or just exchange the view from the normal controller handling with your mobile view.
Ok, warnings:
this is only a proof of concept of what I understood must be done so:
+#MobileViewEnable and #MobileView annotated (and related) methods need to stay in the same controller
+there's no check for the httpAction used
+the two methods must have the same signature
+mobileView annotation value and requestMapping annotation value must be equals and uniques
+the logic inside callYourLogic(..) defines which method is going to be called, at the moment there's a very simple logic that check if exist the parameter ("mobile") in the request, just to test
+this code is not intended to be used as is (at all)
+don't know if it works at all outside my pc (joke :D, ehm..)
SO:
Annotations:
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileView {
String value() default "";
}
#Retention(RetentionPolicy.RUNTIME)
public #interface MobileViewEnable {
}
ExampleController:
#Controller
public class MainController extends BaseController {
private final static Logger logger = LoggerFactory.getLogger(MainController.class);
private final static String PROVA_ROUTE = "prova";
#MobileViewEnable
#RequestMapping(PROVA_ROUTE)
public String prova() {
logger.debug("inside prova!!!");
return "provaview";
}
#MobileView(PROVA_ROUTE)
public String prova2() {
logger.debug("inside prova2!!!");
return "prova2view";
}
}
Aspect definition:
<bean id="viewAspect" class="xxx.yyy.ViewAspect" />
<aop:config>
<aop:pointcut expression="#annotation(xxx.yyy.MobileViewEnable)" id="viewAspectPointcut" />
<aop:aspect ref="viewAspect" order="1">
<aop:around method="around" pointcut-ref="viewAspectPointcut" arg-names="viewAspectPointcut"/>
</aop:aspect>
</aop:config>
Aspect implementation:
public class ViewAspect implements BeforeAdvice, ApplicationContextAware {
private final static Logger logger = LoggerFactory.getLogger(ViewAspect.class);
private ApplicationContext applicationContext;
public Object around(ProceedingJoinPoint joinPoint) {
Method mobileViewAnnotatedMethod = null;
HttpServletRequest request = getCurrentHttpRequest();
String controllerName = getSimpleClassNameWithFirstLetterLowercase(joinPoint);
Object[] interceptedMethodArgs = getInterceptedMethodArgs(joinPoint);
String methodName = getCurrentMethodName(joinPoint);
Method[] methods = getAllControllerMethods(joinPoint);
Method interceptedMethod = getInterceptedMethod(methods, methodName);
String interceptedMethodRoute = getRouteFromInterceptedMethod(interceptedMethod);
if (callYourLogic(request)) {
mobileViewAnnotatedMethod = getMobileViewAnnotatedMethodWithRouteName(methods, interceptedMethodRoute);
if (mobileViewAnnotatedMethod != null)
return invokeMethod(mobileViewAnnotatedMethod, interceptedMethodArgs, controllerName);
}
return continueInterceptedMethodExecution(joinPoint, interceptedMethodArgs);
}
private Object continueInterceptedMethodExecution(ProceedingJoinPoint joinPoint, Object[] interceptedMethodArgs) {
try {
return joinPoint.proceed(interceptedMethodArgs);
} catch (Throwable e) {
logger.error("unable to proceed with intercepted method call: " + e);
}
return null;
}
private Object[] getInterceptedMethodArgs(JoinPoint joinPoint) {
return joinPoint.getArgs();
}
private boolean callYourLogic(HttpServletRequest request) {
// INSERT HERE YOUR CUSTOM LOGIC (e.g.: is the server accessed from a mobile device?)
// THIS IS A STUPID LOGIC USED ONLY FOR EXAMPLE
return request.getParameter("mobile")!= null;
}
private HttpServletRequest getCurrentHttpRequest() {
return ((ServletRequestAttributes) RequestContextHolder.currentRequestAttributes()).getRequest();
}
private String invokeMethod(Method method, Object[] methodArgs, String className) {
if (method != null) {
try {
Object classInstance = getInstanceOfClass(method, className);
return (String) method.invoke(classInstance, methodArgs);
} catch (Exception e) {
logger.error("unable to invoke method" + method + " - " + e);
}
}
return null;
}
private Object getInstanceOfClass(Method method, String className) {
return applicationContext.getBean(className);
}
private Method getMobileViewAnnotatedMethodWithRouteName(Method[] methods, String routeName) {
for (Method m : methods) {
MobileView mobileViewAnnotation = m.getAnnotation(MobileView.class);
if (mobileViewAnnotation != null && mobileViewAnnotation.value().equals(routeName))
return m;
}
return null;
}
private String getRouteFromInterceptedMethod(Method method) {
RequestMapping requestMappingAnnotation = method.getAnnotation(RequestMapping.class);
if (requestMappingAnnotation != null)
return requestMappingAnnotation.value()[0];
return null;
}
private String getCurrentMethodName(JoinPoint joinPoint) {
return joinPoint.getSignature().getName();
}
private Method[] getAllControllerMethods(JoinPoint joinPoint) {
return joinPoint.getThis().getClass().getSuperclass().getMethods();
}
private String getSimpleClassNameWithFirstLetterLowercase(JoinPoint joinPoint) {
String simpleClassName = joinPoint.getThis().getClass().getSuperclass().getSimpleName();
return setFirstLetterLowercase(simpleClassName);
}
private String setFirstLetterLowercase(String simpleClassName) {
String firstLetterOfTheString = simpleClassName.substring(0, 1).toLowerCase();
String restOfTheString = simpleClassName.substring(1);
return firstLetterOfTheString + restOfTheString;
}
private Method getInterceptedMethod(Method[] methods, String lookingForMethodName) {
for (Method m : methods)
if (m.getName().equals(lookingForMethodName))
return m;
return null;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}

Resources