Failed to call verify() a mock that is proxied by MethodValidationPostProcessor - java

I have a simple service interface:
#Service
#Validated
public interface UserService
{
void createUser (#Email String userEmail);
void refreshUsers ();
}
I want to test 2 things on UserService:
The validation annotation #Email is working.
The calls on method refreshUsers() can be verified by verify().
Therefore, I created the following test case with SpringJUnit4ClassRunner:
#RunWith(value = SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = UserServiceTest.TestConfig.class)
public class UserServiceTest
{
#Configuration
public static class TestConfig
{
#Bean
public UserService userService ()
{
return mock(UserService.class);
}
#Bean
public MethodValidationPostProcessor methodValidationPostProcessor ()
{
return new MethodValidationPostProcessor();
}
}
#Autowired
private UserService userService;
#Test
public void createUserWithValidEmail ()
{
userService.createUser("aaa#example.com");
}
#Test
public void createUserWithInvalidEmail ()
{
assertThatThrownBy(() -> userService.createUser("aaa"))
.isInstanceOf(ConstraintViolationException.class);
}
#Test
public void refreshUsersCalled1 ()
{
userService.refreshUsers();
verify(userService).refreshUsers();
}
#Test
public void refreshUsersCalled2 ()
{
userService.refreshUsers();
verify(userService).refreshUsers();
}
The test case refreshUsersCalled2() will fail with the following message:
Missing method call for verify(mock) here:
-> at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Example of correct verification:
verify(mock).doSomething()
Also, this error might show up because you verify either of: final/private/equals()/hashCode() methods.
Those methods *cannot* be stubbed/verified.
Mocking methods declared on non-public parent classes is not supported.
However, the method refreshUsers() is not one of final/private/equals()/hashCode() methods.
After some investigation, I found that the problem came from the conflict between mock() and MethodValidationPostProcessor.
If I remove the MethodValidationPostProcessor Spring bean from TestConfig, the above issues will be solved. However, the test case createUserWithInvalidEmail will fail because the validation mechanism is gone.
Is there any way to test both validation and verify() in the same test?
The full example is at https://github.com/johnlinp/misc-demo/tree/master/mockito-with-validation-in-spring-test.

Related

Retryable annotation - Junit5 - Mockito - is it possible

Is it possible to write unit test using Junit 5 mockito for retryable annotations?
I am having a service interface which has only one method, which downloads the file from remote url
#service
interface downloadpdf{
#Retryable(value = { FileNotFoundException.class, HttpClientErrorException.class }, maxAttempts = 5, backoff = #Backoff(delay = 1000))
public string downloadpdffile(string remoteurl, string pdfname);
}
I have tried referring sites and found using Spring4JunitRunner implementation to test retry. Got confused with implementation. Is it possible to write unit test using Junit 5 mockito for retryable annotations?. Could you please elaborate on the solution here?
You need to use #SpringJUnitConfig (which is the equivalent of the JUnit4 runner). Or #SpringBootTest as you are using Boot.
#Retryable only works with beans managed by Spring - it wraps the bean in a proxy.
#SpringBootApplication
#EnableRetry
public class So71849077Application {
public static void main(String[] args) {
SpringApplication.run(So71849077Application.class, args);
}
}
#Component
class RetryableClass {
private SomeService service;
void setService(SomeService service) {
this.service = service;
}
#Retryable
void retryableMethod(String in) {
service.callme();
throw new RuntimeException();
}
#Recover
void recover(Exception ex, String in) {
service.failed();
}
}
interface SomeService {
void callme();
void failed();
}
#SpringBootTest
class So71849077ApplicationTests {
#MockBean
SomeService service;
#Test
void testRetry(#Autowired RetryableClass retryable) {
SomeService service = mock(SomeService.class);
retryable.setService(service);
retryable.retryableMethod("foo");
verify(service, times(3)).callme();
verify(service).failed();
}
}
I was also trying to implement this using Junit5.
Tried various options but that didn't help. Then after googling for few hours, got the following link and it helped to succeed.
https://doctorjw.wordpress.com/2022/04/29/spring-testing-a-single-bean-in-junit-5-springextension/
Reference code below, for detailed explanation, please refer the blog.
#Component
public class MyClass {
private ObjectMapper objectMapper;
private RestTemplate restTemplate;
#Value("${testValue:5}")
private int value;
#Retryable(....)
public void doStuff() throws SomeException {
...
}
}
What I’ve discovered is, if I declare my test class this way:
#ExtendWith( SpringExtension.class )
#Import( { MyClass.class, ObjectMapper.class } )
#EnableRetry
public class MyClassTest {
#Autowired
private MyClass myClass;
#MockBean
private RestTemplate restTemplate;
#Autowired
private ObjectMapper objectMapper;
#BeforeEach
public void setup() {
// If we are going to jack with the object configuration,
// we need to do so on the actual object, not the Spring proxy.
// So, use AopTestUtils to get around the proxy to the actual obj.
TestingUtils.setFieldValue( AopTestUtils.getTargetObject( myClass ), "value", 10 );
}
}
You will notice the inclusion of 1 other class, TestingUtils.class. This class looks like:
public class TestingUtils {
public static void setFieldValue( Object object, String fieldName, Object value ) {
Field field = ReflectionUtils.findField( object.getClass(), fieldName );
ReflectionUtils.makeAccessible( field );
ReflectionUtils.setField( field, object, value );
}
}
All credits goes to the author of the blog.

Mockito when isn't replacing original method behaviour

I got 2 modules User and Email, both of them have 1 entry point which is a facade, rest is package scoped. The configuration is done in 2 classes
#Configuration
class UserConfiguration {
#Bean
UserFacade userFacade(UserRepository repository, EmailFacade emailFacade) {
return new UserFacade(repository, emailFacade);
}
}
#Configuration
class EmailConfiguration {
#Bean
EmailFacade emailFacade(EmailSender emailSender) {
return new EmailFacade(emailSender);
}
}
Now, I want to write tests that don't require Spring to start. I implemented a simple InMemoryRepository to make this happen
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade = new EmailFacade(new FakeEmailSender());
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I need some fake objects to instantiate EmailFacade so I wrote fake implementation
public class FakeEmailSender implements EmailSender {
#Override
public void sendEmail(EmailMessage emailMessage) throws RuntimeException {
}
}
In that scenario, I'm testing User domain, so I want to mock Email anyways.
I wrote a test to check if it works
#Test
public void shouldReturnSendingFailed() {
Mockito.when(emailFacade.sendUserVerificationEmail(Mockito.any())).thenReturn(Either.left(EmailError.SENDING_FAILED));
assertThat(userFacade.registerNewUser(RegisterUserDto.builder()
.username(USERNAME_4)
.email(VALID_EMAIL)
.password(VALID_PASSWORD).build()).getLeft(), is(EmailError.SENDING_FAILED));
}
But it isn't... after running this test I got
java.util.NoSuchElementException: getLeft() on Right
edit#
regiserNewUser() method
Either<DomainError, SuccessMessage> register(RegisterUserDto registerUserDto) {
if(userRepository.findUser(registerUserDto.getUsername()).isPresent())
return Either.left(UserError.USERNAME_ALREADY_EXISTS);
var userCreationResult = User.createUser(registerUserDto);
var savedUser = userCreationResult.map(this::saveUser);
var emailDto = savedUser.map(this::createVerificationEmail);
return emailDto.isRight() ? emailFacade.sendUserVerificationEmail(emailDto.get())
: Either.left(emailDto.getLeft());
}
Edit2#
With following test configuration
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
#InjectMocks
private UserFacade userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
#Before
public void setUp() {
MockitoAnnotations.initMocks(this);
}
}
I got nullpointer here, last line of registerNewUser().
Try running this code
#RunWith(MockitoJUnitRunner.class)
public class RegisterUserTest {
#Mock
private EmailFacade emailFacade;
private UserFacade userFacade;
#Before
public void setUp() {
userFacade = new UserConfiguration().userFacade(new InMemoryUserRepository(), emailFacade);
}
}
There are a few issues with your code:
You initialize your mocks twice. You don’t need to call initMocks in the setUp method if you are using Mockito runner
You are trying to inject mocks to already initialized object. But the field you are trying to inject is also passed to the constructor. Please read #InjectMocks doc, to check the strategies used to inject the mocks:
constructor (not used here, already initialized object)
setter (do you have one?)
field (is it not final)
There are details to each strategy (see my questions above). If no staregy is matched, Mockito will fail silently. The fact that you are passing an object in constructor, and rely on setter or field injection afterwards makes this code unnecesarily complex.

How to invoke #BeforeMethod block before #PostConstruct

I am writing below Spring Unit test code. Unit test #Before method is not getting executed. Since it is directly running #PostConstruct i am getting erorrs Caused by: java.lang.IllegalArgumentException: rate must be positive because the default value is 0.00. I want to set some value to request max limit so that postcontstruct block will go through smoothly. what is wrong in my code? Please help.
#Component
public class SurveyPublisher {
#Autowired
private SurveyProperties surveyProperties;
#PostConstruct
public void init() {
rateLimiter = RateLimiter.create(psurveyProperties.getRequestMaxLimit());
}
}
public void publish() {
rateLimiter.acquire();
// do something
}
}
//Unit test class
public class SurveyPublisherTest extends AbstractTestNGSpringContextTests {
#Mock
SurveyProperties surveyProperties;
#BeforeMethod
public void init() {
Mockito.when(surveyProperties.getRequestMaxLimit()).thenReturn(40.00);
}
#Test
public void testPublish_noResponse() {
//do some test
}
}
Just realized it will always run postConstruct method before Junit callback methods cause spring takes the precedence. As explained in the documentation -
if a method within a test class is annotated with #PostConstruct, that
method runs before any before methods of the underlying test framework
(for example, methods annotated with JUnit Jupiter’s #BeforeEach), and
that applies for every test method in the test class.
Solution to you issue -
As #chrylis commented above refactor your SurveyPublisher to use constructor injection to inject the rate limiter. So you can then easily test.
Inject Mock/Spy bean which is causing the problem
Create test config to give you the instance of the class to use as #ContextConfiguration
#Configuration
public class YourTestConfig {
#Bean
FactoryBean getSurveyPublisher() {
return new AbstractFactoryBean() {
#Override
public Class getObjectType() {
return SurveyPublisher.class;
}
#Override
protected SurveyPublisher createInstance() {
return mock(SurveyPublisher.class);
}
};
}
}
Here is the simple one worked.
#Configuration
#EnableConfigurationProperties(SurveyProperties.class)
static class Config {
}
#ContextConfiguration(classes = {
SurveyPublisherTest.Config.class })
#TestPropertySource(properties = { "com.test.survey.request-max-limit=1.00" })
public class SurveyPublisherTest extends AbstractTestNGSpringContextTests {
//Remove this mock
//#Mock
//SurveyProperties surveyProperties;
}

How to mock Asynchronous (#Async) method in Spring Boot using Mockito?

What is the best way to mock asynchronous (#Async) method with mockito? Provided service below:
#Service
#Transactional(readOnly=true)
public class TaskService {
#Async
#Transactional(readOnly = false)
public void createTask(TaskResource taskResource, UUID linkId) {
// do some heavy task
}
}
Mockito's verification as below:
#RunWith(SpringRunner.class)
#WebMvcTest(SomeController.class)
public class SomeControllerTest {
#Autowired
MockMvc mockMvc;
#MockBean
private TaskService taskService;
#Rule
public MockitoRule mockitoRule = MockitoJUnit.rule();
// other details omitted...
#Test
public void shouldVerify() {
// use mockmvc to fire to some controller which in turn call taskService.createTask
// .... details omitted
verify(taskService, times(1)) // taskService is mocked object
.createTask(any(TaskResource.class), any(UUID.class));
}
}
The test method shouldVerify above will always throw:
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Misplaced argument matcher detected here:
-> at SomeTest.java:77) // details omitted
-> at SomeTest.java:77) // details omitted
You cannot use argument matchers outside of verification or stubbing.
Examples of correct usage of argument matchers:
when(mock.get(anyInt())).thenReturn(null);
doThrow(new RuntimeException()).when(mock).someVoidMethod(anyObject());
verify(mock).someMethod(contains("foo"))
Also, this error might show up because you use argument matchers with methods that cannot be mocked.
Following methods *cannot* be stubbed/verified: final/private/equals()/hashCode().
Mocking methods declared on non-public parent classes is not supported.
The exception above won't happend if I remove #Async from the TaskService.createTask method.
Spring Boot version: 1.4.0.RELEASE
Mockito version: 1.10.19
Found out that by changing the Async mode to AspectJ fixed the issue:
#EnableCaching
#SpringBootConfiguration
#EnableAutoConfiguration
#ComponentScan(lazyInit = true)
#EnableAsync(mode = AdviceMode.ASPECTJ) // Changes here!!!
public class Main {
public static void main(String[] args) {
new SpringApplicationBuilder().sources(Main.class)
.run(args);
}
}
I'll accept this as a temporary hackish solution until I understand what's the real root cause of this issue.
There's a bug in Spring Boot that we hope to fix in 1.4.1. The problem is that your mock TaskService is still being called asynchronously which breaks Mockito.
You could work around the problem by creating an interface for TaskService and creating a mock of that. As long as you leave the #Async annotation only on the implementation things will then work.
Something like this:
public interface TaskService {
void createTask(TaskResource taskResource, UUID linkId);
}
#Service
#Transactional(readOnly=true)
public class AsyncTaskService implements TaskService {
#Async
#Transactional(readOnly = false)
#Override
public void createTask(TaskResource taskResource, UUID linkId) {
// do some heavy task
}
}

Mockito to test an autowired field

public interface Dummy {
public returnSomething doDummyWork(arg1, agr2);
}
public class A implements Dummy {
#AutoWired
PrintTaskExecutor printTaskExecutor;
public returnSomething doDummyWork(arg1, agr2) {
callingVoidMethod();
return something;
}
public void callingVoidMethod() {
printTaskExecutor.printSomething(arg1, arg2);
}
}
public class testDummy {
#Autowired
Dummy dummyA//this bean is configured in ApplicationContext.xml and it works fine.
#Mock
PrintTaskExecutor printaskExecutor;
#Before
public void initMocks() {
MockitoAnnotations.initMocks(this);
printaskExecutor = Mockito.mock(PrintTaskExecutor.class);
Mockito.doNothing().when(printaskExecutor).printSomething(anyString(), anyString());
}
#Test
Public void testA
{
Dummy.doDummyWork(arg1, arg2);//I m giving actual arguments
//instead of moocking it calls the original method.
Mockito.verify(printaskExecutor, times(1)).printSomething(anyString(), anyString());
}
}
I have an autowired TaskExecutor in the class I m testing and I want to mock it.I have tried this in my code and It calls the actual method instead of do nothing and in the verify it errors out saying no interactions happened. How should I handle this situation?
I try to avoid using Mockito and Bean Containers together in one test. There are solutions for that problem. If you use Spring you should use #RunWith(SpringJUnit4Runner.class). More on this subject: Injecting Mockito mocks into a Spring bean
The clean way: Actually your class testDummy does not test Dummy but A. So you can rewrite your class in following way:
public class testA {
#Mock
PrintTaskExecutor printTaskExecutor;
#InjectMocks
A dummyA;
...
BTW: #Mock together with initMocks(this) and printaskExecutor = Mockito.mock(PrintTaskExecutor.class); do the same, you can skip the latter statement.

Categories

Resources