I have this Controller class that I want to test:
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
#Autowired
public AuthController(UserService service, PasswordEncoder encoder) {
this.service = service;
this.encoder = encoder;
}
#Override
public ResponseEntity<SignedInUser> register(#Valid NewUserDto newUser) {
Optional<SignedInUser> createdUser = service.createUser(newUser);
LoggerFactory.getLogger(AuthController.class).info(String.valueOf(createdUser.isPresent()));
if (createdUser.isPresent()) {
return ResponseEntity.status(HttpStatus.CREATED).body(createdUser.get());
}
throw new InsufficientAuthentication("Insufficient info");
}
This is my unit test:
#ExtendWith(MockitoExtension.class)
#JsonTest
public class AuthControllerTest {
#InjectMocks
private AuthController controller;
private MockMvc mockMvc;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
private static SignedInUser testSignedInUser;
private JacksonTester<SignedInUser> signedInTester;
private JacksonTester<NewUserDto> dtoTester;
#BeforeEach
public void setup() {
ObjectMapper mapper = new AppConfig().objectMapper();
JacksonTester.initFields(this, mapper);
MappingJackson2HttpMessageConverter mappingConverter = new MappingJackson2HttpMessageConverter();
mappingConverter.setObjectMapper(mapper);
mockMvc = MockMvcBuilders.standaloneSetup(controller)
.setControllerAdvice(new RestApiErrorHandler())
.setMessageConverters(mappingConverter)
.build();
initializeTestVariables();
}
private void initializeTestVariables() {
testSignedInUser = new SignedInUser();
testSignedInUser.setId(1L);
testSignedInUser.setRefreshToken("RefreshToken");
testSignedInUser.setAccessToken("AccessToken");
}
#Test
public void testRegister() throws Exception {
NewUserDto dto = new NewUserDto();
dto.setEmail("ttn.nguyen42#gmail.com");
dto.setPassword("ThisIsAPassword");
dto.setName("ThisIsAName");
// Given
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser));
// When
MockHttpServletResponse res = mockMvc.perform(post("/api/v1/auth/register")
.contentType(MediaType.APPLICATION_JSON)
.content(dtoTester.write(dto).getJson()).characterEncoding("utf-8").accept(MediaType.APPLICATION_JSON))
.andDo(MockMvcResultHandlers.print())
.andReturn()
.getResponse();
// Then
assertThat(res.getStatus()).isEqualTo(HttpStatus.CREATED.value());
assertThat(res.getContentAsString()).isEqualTo(signedInTester.write(testSignedInUser).getJson());
}
}
Problem:
The test failed as I got the "Insufficient info" message, because isPresent() is never true, even with given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser)) already there.
When I try to log service.createUser(dto) inside the test method, its isPresent() is always true.
When I try to log inside the controller method, it is always false.
What I have tried:
I suspect that it is because somehow my mockMvc is wrongly configured so I tried to add #AutoConfigureMockMvc but it ended up telling me that "There is no bean configured for 'mockMvc'". I tried to change the UserService mock to the implementation class of but no use.
Please help, I'm really new into Spring's unit tests. Thank you!
The problem is as you have found the Mockito mocking:
given(service.createUser(dto)).willReturn(Optional.of(testSignedInUser))
Specifically, you instruct the mocked service to return an Optional.of(testSignedInUser) if it receives a parameter that is equal to dto. However, depending on the implementation of the equals() method of NewUserDto, this may never occur. For example it returns true only if the same instance is referred to instead of comparing the values of the member variables. Consequently, when passing a dto through the mockMvc it is first serialized and then serialized again by the object mapper, so even though its member variables have the same values, the objects are not considered equal unless you also override the equals() method.
As an alternative, you can relax the mocking to return the Optional.of(testSignedInUser) if any() argument is passed:
given(service.createUser(any())).willReturn(Optional.of(testSignedInUser))
or if the argument isA() specific class:
given(service.createUser(isA(NewUserDto.class))).willReturn(Optional.of(testSignedInUser))
but generally, it is preferred from a testing perspective to be explicit to avoid false positives so for this reason I advise to double check and and / or override the NewUserDto#equals() method if possible.
Thanks to #matsev answer, I was able to solve half of the problem, but there was this thing as well:
#Controller
public class AuthController implements AuthApi {
private final UserService service;
private final PasswordEncoder encoder;
// ...
}
I left the service fields as final, which did not allow Mockito to inject or mutate the dependency. After removing final, I got the test to work now.
EDIT: Please refer to this thread for workaround Mockito, #InjectMocks strange behaviour with final fields
EDIT 2: By design, Mockito does not inject mocks to final fields: https://github.com/mockito/mockito/issues/352
Doing so violates other APIs and can cause issues. One way to fix this is to just use constructor injection, remove #InjectMocks, then you can just use final fields.
// No InjectMocks
private AuthController controller;
#Mock
private UserService service;
#Mock
private PasswordEncoder encoder;
#BeforeEach
public void setup() {
controller = new AuthController(service, encoder);
// ...
}
Related
I'm trying to test service layer using Mockito with JUnit but each time when I run it return object gets nulled.
More info:
UserRepository is a plain spring data repository extending CRUDRepository.
User is a plain JPA entity.
and test:
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
private UserRepository userRepository;
#InjectMocks
private UserService userService = new UserService();
private User user;
#Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
}
#Test
public void tesGetUserIdStatus() {
// given
user = new User();
user.setUserName(userName);
user.setUserId(userId);
User newUser = new User();
// when
Mockito.when(userRepository.findByUserId(userId)).thenReturn(newUser);
User result = userService.getUserById(user);
// then
Assert.assertEquals(newUser, user);
}
}
That test will end up that expected object values get nulled when actual are correct.
Service part which I want to test looks like:
#Component
public class UserService {
private final UserRepository userRepository;
public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}
public User getUserById(User user) throws EntityNotFoundException {
String userId = user.getUserId();
user = userRepository.findByUserId(userId);
return user;
}
...
I see there's a design issue as well in addition to improper user of mockito. The #InjectMocks is not a good choice. It's implementation is flawed, here's what their javadocs say.
Mockito will try to inject mocks only either by constructor
injection, * setter injection, or property injection in order and as
described below. * If any of the following strategy fail, then
Mockito won't report failure; * i.e. you will have
to provide dependencies yourself.
I think it should be deprecated so developers could nicely do constructor injection. Have a look at this approach;
Refactor the UserService so you could inject the UserRepository via UserService's constructor.
For example: https://docs.spring.io/spring-boot/docs/current/reference/html/using-boot-spring-beans-and-dependency-injection.html
Then
#RunWith(MockitoJUnitRunner.class)
public class UserServiceTest {
#Mock
private UserRepository userRepository;
private UserService userService;
private User user;
#Before
public void setUp() throws Exception {
userService = new UserService(userRepository);
}
#Test
public void tesGetUserIdStatus() {
//your test code
}
This is good practice and you could test your logic (services, helpers etc) in isolation with right mocks and stubbing.
However, if you want to verify the correct injection of resources by spring ioc container and want to do operations like validation, you should consider proper Spring/Spring boot tests. Have a look at official Spring testing docs -- https://docs.spring.io/spring-boot/docs/current/reference/html/boot-features-testing.html
By findByUserId(UserId) you mean findByUserId(userId) and by
Assert.assertEquals(newUser, user) you mean Assert.assertEquals(result, user)? And where do the values userName and userId come from?
And please don't use #InjectMocks to inject an UserService
try this instead:
#Autowired
private UserService userService;
or at least
#InjectMock
private UserService userService;
Your test may fail because the Repository inside UserService cannot be injected properly and the returned result will be null.
There are a few issues in your test:
UserService is instantiated by Mockito (#InjectMocks) - do not initialise it with a new UserService instance - that is the reason you are getting NPE
since you are using MockitoJUnitRunner you do not need to explicitly call MockitoAnnotations.initMocks
when(userRepository.findByUserId(UserId)) - what is UserId?
calling Mockito.when() should be a part of the given section
in the then section you should assert the value you have obtained in the when section
I have this class:
SqssReadderApp:
#Autowired
private Source source;
#Autowired
private AWSProperties awsProperties;
#Autowired
private AuditRepository auditRepository;
private AmazonSQS sqs;
#Autowired
public SqssreaderApplication(AWSConfig awsConfig) {
this.sqs = awsConfig.generateSQS();
}
It is calling a method generateSQS() from its constructor. The SQS is generated by invoking a static method.
Here is the relevant class containing the method:
#Autowired
private AWSProperties awsProperties;
#Bean
AmazonSQS generateSQS() {
return AmazonSQSClientBuilder.standard()
.withRegion(awsProperties.getQueueRegion())
.build();
}
Now, in my test class, I'm trying to mock this generateSQS() method call by injecting a mockAwsConfig.
I think there are two approches to inject the mockSqs instance in the SqssReaderApplication.
Approach 1:
I inject the mockAwsConfig into the constructor and initialize it with this rule:
when(mockAwsConfig.generateSQS()).thenReturn(mockSQS);
SqssReaderApplication app = new SqssReaderApplication(mockAwsConfig);
problem:
All the remianing (#Autowired) entities remain null.
Approach 2:
I #Autowired the SqssReaderApplication, all the other fields are mocked, but the sqs instance in the SqssReaderApplication remain null.
**TestClass**:
#RunWith(SpringRunner.class)
#ActiveProfiles("test")
#TestPropertySource(locations = "classpath:application-test.properties")
#SpringBootTest
#ContextConfiguration(classes = {SqssreaderApplication.class,
DefaultFrameworkSupport.class, AWSConfig.class, AuditRepository.class, FileProcessAuditLog.class})
public class SqssreaderApplicationTests {
#Autowired
private Source source;
#MockBean
AuditRepository auditRepository;
#Mock
private AmazonSQSClient amazonSQS;
#Mock
private AWSConfig awsConfig;
#InjectMocks
private SqssreaderApplication app;
#Mock
private AWSProperties awsProperties;
#Before
public void setup(){
MockitoAnnotations.initMocks(this);
when(awsConfig.generateSQS()).thenReturn(amazonSQS);
app = new SqssreaderApplication(awsConfig);
}
I have seen other answers too, but they don't work in this scenario.
What could be other approach to this problem? How should I inject the mocks in the SqssReaderApp, preferably without using PowerMock or modifying the source code?
You are mixing two ways of dependency injection (https://en.wikipedia.org/wiki/Dependency_injection#Three_types_of_dependency_injection). You should create proper constructor and only this construstor should be #Autowired.
Your class should look something like:
private Source source;
private AWSProperties awsProperties;
private AuditRepository auditRepository;
private AmazonSQS sqs;
#Autowired
public SqssreaderApplication(Source source, AWSProperties awsProperties, AuditRepository auditRepository, AWSConfig awsConfig) {
this.sqs = awsConfig.generateSQS();
Then in your test you can modify your #1 Approach and add all required mocks via constructor.
Little offtop: You are using AWSProperties both in AWSConfig and SqssreaderApplication classes. Check if you really need it in SqssreaderApplication.
AmazonSQS is also a spring bean which can be injected with #Autowired. So you can change the constructor from
#Autowired
public SqssReadderApp(AWSConfig awsConfig) {
this.sqs = awsConfig.generateSQS();
}
to
#Autowired
public SqssReadderApp(AmazonSQS sqs) {
this.sqs = sqs;
}
Now you can inject your mocked instance into the constructor.
You should also consider to resolve this mix of field injection and constructor injection. I recommend constructor injection which makes testing a lot easier.
public class SqssReadderApp {
#Autowired
public SqssReadderApp(Source source, AWSProperties awsProperties, AuditRepository auditRepository, AmazonSQS sqs) {
//
}
}
See also why field injection is evil.
This is my PersonSeviceImpl class. Here before saving any person I want to run some validators which implements PersonValidator. And also I autowired PersonStatusChangeValidator to use in a different method.
public class PersonServiceImpl implements PersonService {
#Autowired
PersonDao personDao;
#Autowired
List<PersonStatusChangeValidator> statusChangeValidators;
#Autowired
List<PersonValidator> personValidators;
#Override
public Person save(Person person) {
for(PersonValidator validator: personValidators){
validator.validate(person);
}
personDao.save(person);
return person;
}
}
Here I am writing test to verify whether validators are called or not.
#RunWith(PowerMockRunner.class)
public class PersonServiceImplTest {
#Mock
private PersonDao personDao;
#Mock
private PersonValidator personValidator;
#Mock
private PersonStatusChangeValidator statusChangeValidator;
#Spy
private List<PersonStatusChangeValidator> statusChangeValidators = new ArrayList<>();
#Spy
private List<PersonValidator> personValidators = new ArrayList<>();
#InjectMocks
private PersonServiceImpl personService;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
personValidators.add(personValidator);
statusChangeValidators.add(statusChangeValidator);
}
#Test
public void testCreatePerson() throws Exception {
personService.save(new Person());
verify(personValidator, times(1)).validate(any(Person.class));
}
}
The problem is personValidators, statusChangeValidators are null by the time I'm running test. And when there is a single #Spy annotation in the test, it is working fine. Need some help to know where I'm doing wrong.
Your test doesn't show any mocking of statics or finals so I'm wondering why you are using #RunWith(PowerMockRunner.class)?
I have taken your code and successfully run it with just one change: replacing #RunWith(PowerMockRunner.class) with #RunWith(MockitoJUnitRunner.class).
So, unless there is some other aspect of your tests which require PowerMock then I'd suggest running your test case with MockitoJUnitRunner. If there is some aspect of your tests which require PowerMock then I'd suggest injecting the statusChangeValidators and personValidators instances with explicit constructor injection (i.e. what #JB Nizet suggested in his comment above) so that you do not have to rely on #InjectMocks.
FWIW, there's an open issue against PowerMock for this inability to apply #InjectMocks for fields with the #Spy annotation.
I'm developing a small application in Java using Spring, so I have this Service:
public class AccountService implements UserDetailsService {
#Autowired
private AccountRepository accountRepository;
#Autowired
private BlogRepository blogRepository;
#Autowired
private ImageService imageService;
#PostConstruct
protected void initialize() throws IOException {
Account user = new Account("user", "demo", "ROLE_USER");
save(user);
Blog userBlog = new Blog("userBlog", true, user);
userBlog.setAvatar(imageService.createBlogAvatar(userBlog.getName()));
blogRepository.save(userBlog);
save(new Account("admin", "admin", "ROLE_ADMIN"));
}
// More methods
}
And this test:
#RunWith(MockitoJUnitRunner.class)
public class AccountServiceTest {
#InjectMocks
private AccountService accountService = new AccountService();
#Mock
private AccountRepository accountRepositoryMock;
#Test
public void shouldInitializeWithTwoDemoUsers() throws IOException {
// act
accountService.initialize();
// assert
verify(accountRepositoryMock, times(2)).save(any(Account.class));
}
}
Why when I run the tests I get this exception?
shouldInitializeWithTwoDemoUsers(es.udc.fi.dc.fd.account.AccountServiceTest) Time elapsed: 0.016 sec <<< ERROR!
java.lang.NullPointerException: null
at es.udc.fi.dc.fd.account.AccountService.initialize(AccountService.java:45)
at es.udc.fi.dc.fd.account.AccountServiceTest.shouldInitializeWithTwoDemoUsers(AccountServiceTest.java:42)
Using the #PostConstruct annotation it's supposed to have all beans injected right?
Few things here. First of all #InjectMocks generally makes things easier but Mockito not a dependency injection framework, so its not guaranteed to work properly.
Secondly, for #InjectMocks to work properly you need to #Mock all your objects as well and not manually create the class you are trying to inject. I don't believe its the case anymore but in order versions of mockito, the order of the #Mocks would matter as well.
This code might work for you
#RunWith(MockitoJUnitRunner.class)
public class AccountServiceTest {
#Mock
private AccountRepository accountRepositoryMock;
#Mock
private BlogRepository blogRepository;
#Mock
private ImageService imageService;
#InjectMocks
private AccountService accountService ;
#Test
public void shouldInitializeWithTwoDemoUsers() throws IOException {
// act
accountService.initialize();
// assert
verify(accountRepositoryMock, times(2)).save(any(Account.class));
}
}
You need to mock all the dependencies that your test subject is using. You may want to do this in your AccountServiceTest class:
#Mock
private BlogRepository blogRepositoryMock;
I've read plenty of articles about how to mock Spring's bean and their autowired fields. But there is nothing I could find about autowired lists of beans.
Concrete problem
I've a class called FormValidatorManager. This class loop through several validators which implements IFormValidator.
#Component
public class FormValidatorManager implements IValidatorManager {
#Autowired
private List<IFormValidator> validators;
#Override
public final IFieldError validate(ColumnDTO columnToValidate, String sentValue) {
String loweredColName = columnToValidate.getName().toLowerCase();
IFieldError errorField = new FieldError(loweredColName);
for (IEsmFormValidator validator : validators) {
List<String> errrorsFound = validator.validate(columnToValidate, sentValue);
//les erreurs ne doivent pas ĂȘtre cumulĂ©es.
if(CollectionUtils.isNotEmpty(errrorsFound)){
errorField.addErrors(errrorsFound);
break;
}
}
return errorField;
}
}
I would like to test this class. But I can't find a way to mock validators property.
What I've tried
Since IFormValidators are singleton, I tried to mock several instances of these beans hoping them to be reflected in FormValidatorManager.validators but without success.
Then, I tried to create a list of IFormValidators which was annotated as #Mock. By initiating the List manually, I was hoping initMocks() to inject the created list. That was still without success.
Here is my last try:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations={"classpath:spring/test-validator-context.xml"})
public class FormValidatorManagerTest {
#Mock
private RegexValidator regexValidator;
#Mock
private FormNotNullValidator notNullValidator;
#Mock
private FormDataTypeValidator dataValidator;
#InjectMocks
private FormValidatorManager validatorManager;
#Mock
private List<IEsmFormValidator> validators = new ArrayList<IEsmFormValidator>();
#Mock
private ColumnDTO columnDTO;
#Before
public void init() {
validators.add(notNullValidator);
validators.add(regexValidator);
validators.add(dataValidator);
MockitoAnnotations.initMocks(this);
Mockito.when(columnDTO.getTitle()).thenReturn("Mock title");
Mockito.when(columnDTO.getName()).thenReturn("Mock name");
}
#Test
public void testNoErrorFound(){
mockValidator(notNullValidator, new ArrayList<String>());
mockValidator(regexValidator, new ArrayList<String>());
mockValidator(dataValidator, new ArrayList<String>());
IFieldError fieldErrors = validatorManager.validate(columnDTO, "Not null value");
Assert.assertEquals(0, fieldErrors.getErrors().size());
verifyNumberOfValidateCalls(regexValidator, Mockito.atMost(1));
verifyNumberOfValidateCalls(dataValidator, Mockito.atMost(1));
verifyNumberOfValidateCalls(notNullValidator, Mockito.atMost(1));
}
private void mockValidator(IFormValidator validator, List<String> listToReturn){
Mockito.when(validator.validate(Mockito.any(ColumnDTO.class), Mockito.anyString())).thenReturn( listToReturn );
}
private void verifyNumberOfValidateCalls(IFormValidator validator, VerificationMode verifMode){
Mockito.verify(validator, verifMode).validate(Mockito.any(ColumnDTO.class), Mockito.anyString());
}
}
An NPE is thrown in IFormValidator.validate() which I thougth would be mocked. The concrete implementation should not be called.
This leads to a really bad behavior since some of my tests on that class are false positives while others completly fail.
I'm trying to figure out how to mock an autowired list of beans while still having the possibility to mock specific implementations.
Do you have an idea start of solution ?
Regards
I finally figured it out...
Sometimes, asking a question can give you a better approach to your problems :p
The problem is I was linking the validators to the list before they were mocked. The validators was then null and no reference could be updated when the MockitAnnotations.initMocks(this) was called.
Moreover, to avoid iterator problems on List, I had to use #Spy instead of #Mock.
Here is the final solution:
#Mock
private EsmRegexValidator regexValidator;
#Mock
private EsmFormNotNullValidator notNullValidator;
#Mock
private EsmFormDataTypeValidator dataValidator;
#InjectMocks
private EsmFormValidatorManager validatorManager;
#Spy
private List<IEsmFormValidator> validators = new ArrayList<IEsmFormValidator>();
#Mock
private ColumnDTO columnDTO;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
validators.add(notNullValidator);
validators.add(regexValidator);
validators.add(dataValidator);
Mockito.when(columnDTO.getTitle()).thenReturn("Mock title");
Mockito.when(columnDTO.getName()).thenReturn("Mock name");
}
Adding another answer when dealing with multiple list of beans. Mockito doesnt know anything about the generics it just uses random list provided so in my case something like this happened.
Which threw the ClassCastException because the bean injection was not perfomed correctly. Expecting SfmcImportRepository but injection was SfmcParser
#Mock SfmcEmailsCsvFileParser emailParser;
#Mock SfmcSmsCsvFileParser smsParser;
#Mock SfmcSmsRepository smsRepository;
#Mock SfmcEmailRepository emailRepository;
List<SfmcImportRepository> sfmcImportRepositories = new ArrayList<>();
List<SfmcParser> sfmcParsers = new ArrayList<>();
SfmcFtpService service;
#Before
public void init() {
sfmcImportRepositories.add(emailRepository);
sfmcImportRepositories.add(smsRepository);
sfmcParsers.add(smsParser);
sfmcParsers.add(emailParser);
service = new SfmcFtpService(sfmcImportRepositories, sfmcParsers);
}
method initMocks is deprecated in the recent versions and is no longer needed:
#Mock
private SomeTxHandler1 txHandler1;
#Mock
private SomeTxHandler2 txHandler2;
#Spy
private final List<TxHandler> txHandlers = new ArrayList<>();
#Spy // if you want to mock your service's methods
#InjectMocks
private MyService myService;
#BeforeEach
public void init() {
lenient().when(txHandler1.accept(...)).thenReturn(true);
txHandlers.add(txHandler1);
lenient().when(txHandler2.accept(...)).thenReturn(true);
txHandlers.add(txHandler2);
}