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);
}
Related
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);
// ...
}
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 writing a test case for a Class which has a 2 level of dependency injection. I use #Spy annotation for the 1 level dependency injection object, and I would like to Mock the 2nd level of injection. However, I kept getting null pointer exception on the 2nd level. Is there any way that I inject the mock into the #Spy object?
public class CarTestCase{
#Mock
private Configuration configuration;
#Spy
private Engine engine;
#InjectMocks
private Car car;
#Test
public void test(){
Mockito.when(configuration.getProperties("")).return("Something");
car.drive();
}
}
public class Car{
#Inject
private Engine engine;
public void drive(){
engine.start();
}
}
public class Engine{
#Inject
private Configuration configuration;
public void start(){
configuration.getProperties(); // null pointer exception
}
}
I've also wandered how to inject a mock into a spy.
The following approach will not work:
#Spy
#InjectMocks
private MySpy spy;
But the desired behavior can be achieved by a "hybrid" approach, when using both annotation and manual mocking. The following works perfectly:
#Mock
private NeedToBeMocked needToBeMocked;
#InjectMocks
private MySpy mySpy;
#InjectMocks
private SubjectUnderTest sut;
#BeforeMethod
public void setUp() {
mySpy = Mockito.spy(new MySpy());
MockitoAnnotations.initMocks(this);
}
(SubjectUnderTest here depends on MySpy, and MySpy in its turn depends on NeedToBeMocked).
UPD: Personally, I think that if you have to do such a magic too often, it might be a sign that there is something wrong with dependenicies between your classes and it is worth to perform a little bit of refactoring to improve your code.
The (simplest) solution that worked for me.
#InjectMocks
private MySpy spy = Mockito.spy(new MySpy());
No need for MockitoAnnotations.initMocks(this) in this case, as long as test class is annotated with #RunWith(MockitoJUnitRunner.class).
Mockito cannot perform such a tricky injections as it's not an injection framework. So, you need to refactor your code to make it more testable. It's easy done by using constructor injection:
public class Engine{
private Configuration configuration;
#Inject
public Engine(Configuration configuration) {
this.configuration = configuration;
}
........
}
public class Car{
private Engine engine;
#Inject
public Car(Engine engine) {
this.engine = engine;
}
}
In this case you have to handle the mocking and injection manually:
public class CarTestCase{
private Configuration configuration;
private Engine engine;
private Car car;
#Before
public void setUp(){
configuration = mock(Configuration.class);
engine = spy(new Engine(configuration));
car = new Car(engine);
}
#Test
public void test(){
Mockito.when(configuration.getProperties("")).return("Something");
car.drive();
}
}
I also met this issue during the unit testing with Spring boot framework, but I found one solution for using both #Spy and #InjectMocks
Previous answer from Yoory N.
#Spy
#InjectMocks
private MySpy spy;
Because InjectMocks need to have instance created, so the solution works for me is at below,
#Spy
#InjectMocks
private MySpy spy = new MySpy();
I think I just found the definitive answer. I tried Yoory approach but changed the order of the annotations :
#InjectMocks
#Spy
private MySpy spy;
I assume that Mockito first creates the mock, and adds a spy on top of that. So there is no need to instantiate the MySpy object.
Junit5 + mockito-junit-upiter-4.2.0 works well
#ExtendWith(MockitoExtension.class)
public class MyTest {
#Spy
#InjectMocks
private SomeClass spy = new SomeClass();
I have a Spring MVC #Controller with this constructor:
#Autowired
public AbcController(XyzService xyzService, #Value("${my.property}") String myProperty) {/*...*/}
I want to write a standalone unit test for this Controller:
#RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {
#Mock
private XyzService mockXyzService;
private String myProperty = "my property value";
#InjectMocks
private AbcController controllerUnderTest;
/* tests */
}
Is there any way to get #InjectMocks to inject my String property? I know I can't mock a String since it's immutable, but can I just inject a normal String here?
#InjectMocks injects a null by default in this case. #Mock understandably throws an exception if I put it on myProperty. Is there another annotation I've missed that just means "inject this exact object rather than a Mock of it"?
You can't do this with Mockito, but Apache Commons actually has a way to do this using one of its built in utilities. You can put this in a function in JUnit that is run after Mockito injects the rest of the mocks but before your test cases run, like this:
#InjectMocks
MyClass myClass;
#Before
public void before() throws Exception {
FieldUtils.writeField(myClass, "fieldName", fieldValue, true);
}
Since you're using Spring, you can use the org.springframework.test.util.ReflectionTestUtils from the spring-test module. It neatly wraps setting a field on a object or a static field on a class (along with other utility methods).
#RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {
#Mock
private XyzService mockXyzService;
#InjectMocks
private AbcController controllerUnderTest;
#Before
public void setUp() {
ReflectionTestUtils.setField(controllerUnderTest, "myProperty",
"String you want to inject");
}
/* tests */
}
You cannot do this with Mockito, because, as you mentioned yourself, a String is final and cannot be mocked.
There is a #Spy annotation which works on real objects, but it has the same limitations as #Mock, thus you cannot spy on a String.
There is no annotation to tell Mockito to just inject that value without doing any mocking or spying. It would be a good feature, though. Perhaps suggest it at the Mockito Github repository.
You will have to manually instantiate your controller if you don't want to change your code.
The only way to have a pure annotation based test is to refactor the controller. It can use a custom object that just contains that one property, or perhaps a configuration class with multiple properties.
#Component
public class MyProperty {
#Value("${my.property}")
private String myProperty;
...
}
This can be injected into the controller.
#Autowired
public AbcController(XyzService xyzService, MyProperty myProperty) {
...
}
You can mock and inject this then.
#RunWith(MockitoJUnitRunner.class)
public class AbcControllerTest {
#Mock
private XyzService mockXyzService;
#Mock
private MyProperty myProperty;
#InjectMocks
private AbcController controllerUnderTest;
#Before
public void setUp(){
when(myProperty.get()).thenReturn("my property value");
}
/* tests */
}
This is not pretty straight forward, but at least you will be able to have a pure annotation based test with a little bit of stubbing.
Just don't use #InjectMocks in that case.
do:
#Before
public void setup() {
controllerUnderTest = new AbcController(mockXyzService, "my property value");
}
Solution is simple: You should put constructor injection for the object type while for primitive/final dependencies you can simply use setter injection and that'll be fine for this scenario.
So this:
public AbcController(XyzService xyzService, #Value("${my.property}") String myProperty) {/*...*/}
Would be changed to:
#Autowired
public AbcController(XyzService xyzService) {/*...*/}
#Autowired
public setMyProperty(#Value("${my.property}") String myProperty){/*...*/}
And the #Mock injections in test would be as simple as:
#Mock
private XyzService xyzService;
#InjectMocks
private AbcController abcController;
#BeforeMethod
public void setup(){
MockitoAnnotations.initMocks(this);
abcController.setMyProperty("new property");
}
And that'll be enough. Going for Reflections is not advisable!
PLEASE AVOID THE USAGE OF REFLECTIONS IN PRODUCTION CODE AS MUCH AS POSSIBLE!!
For the solution of Jan Groot I must remind you that it will become very nasty since you will have to remove all the #Mock and even #InjectMocks and would have to initialize and then inject them manually which for 2 dependencies sound easy but for 7 dependencies the code becomes a nightmare (see below).
private XyzService xyzService;
private AbcController abcController;
#BeforeMethod
public void setup(){ // NIGHTMARE WHEN MORE DEPENDENCIES ARE MOCKED!
xyzService = Mockito.mock(XyzService.class);
abcController = new AbcController(xyzService, "new property");
}
What you can use is this :
org.mockito.internal.util.reflection.Whitebox
Refactor your "AbcController" class constructor
In your Test class "before" method, use Whitebox.setInternalState method to specify whatever string you want
#Before
public void setUp(){
Whitebox.setInternalState(controllerUnderTest, "myProperty", "The string that you want"); }
If you want to have no change in your code then use ReflectionTestUtils.setField method
How do I mock an autowired #Value field in Spring with Mockito?
Is there any way to verify methods call order between mocks if they are created with #Mock annotation?
As described in documentation it can be done with a mock control. But EasyMockRule does not expose control object.
I have looked at EasyMockSupport implementation, but have not found way to force it to use one control for all injected mocks. :(
public class Test extends EasyMockSupport {
#Rule
public EasyMockRule mocks = new EasyMockRule(this);
#Mock
private SomeClass first;
#Mock
private OtherClass second;
#TestSubject
private UnderTest subject = new UnderTest ();
#Test
public void test() {
expect(first.call());
expect(second.call());
....
//Verify that calls were in order first.call(), second.call()
}
}
You are right, it is not possible. An enhancement could be to allow to set a control in the #Mock annotation. Can you please file an issue?
In your case, you will have to create the mocks manually using the same IMocksControl like explained in the documentation.