Null pointer exception on #PostConstruct method? - java

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;

Related

#InjectMocks is not injecting the the list dependency, which is a #Spy

Servcice.java (class to test)
class Service {
#Autowired
private List<Metric> dependency1;
#Autowired
private Executor dependency2;
}
Metric.java : interface
interface Metric{
public void fetchMetric();
}
class Metric1 implements Metric{
public void fetchMetric() {}
}
class Metric2 implements Metric{
public void fetchMetric() {}
}
ServiceTest.java : (test class)
#ExtendWith(MockitoExtension.class)
class ServiceTest {
#Spy
private List<Metric> dependency1;
#Mock
private Executor dependency2;
#InjectMocks // class under test // this has above two as their dependencies.
Service service;
#Mock
private Metric1 metric1;
#Mock
private Metric2 metric2;
#BeforeEach
void setUp() {
// intializing the spy object list with mocks.
this.dependency1 = Arrays.asList(metric1,
metric2
);
}
#Test
void someTest() {
// here in debug mode I can see that `dependency1` as a **spy** and 'dependency1' present in the 'Service' are different, though they should be same.
}
}
Why #InjectMock is not able to Inject a #Spy List dependencies in the Service class object? Am I missing something here.
dependency1 as a spy and dependency1 present as a part of the Service are shown as two different objects on the de-bugger and making the test cases fail. I thought they should be the same.
Should we not initialise the Spies in #BeforeEach method ?
The problem is you use #ExtendWith(MockitoExtension.class) to tell mockito to instantiate your #Mocks #Spy and #InjectMocks. So after Mockito does this you change the field ServiceTest.dependency1 to be a list.
But since Mockito instantiated your CUT with the annotated dependencies, it is not updated when you change the ServiceTest.dependency1.
I would recommend to change Service to have a constructor so you can use the recommended constructor Injection.
That way you can make your test like this:
#ExtendWith(MockitoExtension.class)
class ServiceTest {
#Mock
private Executor dependency2;
Service service;
#Mock
private Metric1 metric1;
#Mock
private Metric2 metric2;
#BeforeEach
void setUp() {
// intializing the spy object list with mocks.
service = new Service(Arrays.asList(metric1,metric2), dependency2);
}
}
If you want the list to be a spy, I would do something like this:
#ExtendWith(MockitoExtension.class)
class ServiceTest {
#InjectMocks
private Service service;
#Mock
private Executor dependency2;
#Spy
private List<Metric> dependency1;
#Test
void test() {
// you can define what to return when there is a method call on the spy dependency1
Metric metric1 = mock(Metric.class);
Metric metric2 = mock(Metric.class);
when(dependency1.get(0)).thenReturn(metric1);
when(dependency1.get(1)).thenReturn(metric2);
service.test();
}
}
One way to handle it:
Remove #Spy and use #Mock on dependency1.
And you will need to handle the List w.r.t. the testcase like:
Mockito.doReturn(metric1).when(dependency1).get(ArgumentMatchers.anyString());

Spring Boot - Unable to mock using Mockito's given(), Optional always empty

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);
// ...
}

Mockito mocking objects from inside local method

In my class, dependencies are injected by Spring. During testing, I am using mocks. I am getting null pointer exception when I call sys.getId("abc12345") in the following code. I am wondering how to write a unit test that gets 100% coverage.
Class under test:
public class SystemUT implements SUTIface{
#Inject
private AccountLookupDAO dao;
#Inject
private OrchService service;
public Response perform(Request req){
String sellerId = getId(request.getSeller().getNum());
String buyerId = null;
if(req.getBuyerId){
buyerId = getId(request.getBuyer().getNum())
}
service.execute(Request,sellerId,buyerId)
}
String getId(String num){
PrefAcct prefAcctObj = dao.lookupPrefId(num,Consants.StrArr);
PrefSysOfRecObj sorObj= prefAcctObj.getSysOfRecord();
return sorObj.getId();
}
}
Unit test:
public Class SystemUTTest{
#Mock
SystemUT sys;
#Mock
AccountLookupDAO daoMock;
#Mock
OrchService serviceMock;
#Mock
PrefAcct prefAcctObj;
#Mock
PrefSysOfRecObj sorObj;
#Before
public void setup(){
Whitebox.setInternalState(sys, daoMock, serviceMock);
}
#Test
public test getId(){
when(dao.lookupPrefId(any(String.class), any(String[].class))).thenReturn(prefAcctObj);
when(prefAcctObj.getSysOfRecord()).thenReturn(sorObj);
when(sorObj.getId()).thenReturn("185");
assertEquals("185",sys.getId("abc12345"));
}
}
Your problem is that your SystemUT class doesn't have its dependencies injected. You could have Spring do this by using their JUnitRunner, but it's not really a unit test then, since you'd be letting Spring dictate which dependencies get injected. Really, you want to control them, and one way to do that is to transform your class to expose its dependencies via a constructor:
public class SystemUT implements SUTIface{
private final AccountLookupDAO dao;
private final OrchService service;
#Inject
public SystemUT(AccountLookupDAO dao, OrchService service) {
this.dao = dao;
this.service = service;
}
}
This will function identically to your current approach since Spring is able to inject dependencies using a constructor annotated with #Inject. Now, when you instantiate your SystemUT class for test, pass mocked objects for its dependencies:
#Mock
AccountLookupDAO daoMock;
#Mock
OrchService serviceMock;
private SystemUT sys;
#Before
public void setup(){
sys = new SystemUT(daoMock, serviceMock);
Whitebox.setInternalState(sys, daoMock, serviceMock);
}

How to prevent Spring from injecting #Autowired references inside a mock?

I want to test a class using Spring + JUnit + Mockito but I don't manage to make it work properly.
Let's say my class references a Service:
#Controller
public class MyController
{
#Autowired
private MyService service;
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
And this Service references a Repository:
#Service
public class MyService {
#Autowired
private MyRepository repository;
public void whatever() {}
public void create() {
repository.save();
}
}
When testing the MyController class, I want the service to be mocked. The problem is: even when the service is mocked, Spring tries to inject the repository in the mock.
Here is what I did. Test class:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { MyControllerTestConfiguration.class })
public class MyControllerTest {
#Autowired
private MyController myController;
#Test
public void testDoSomething() {
myController.doSomething();
}
}
Configuration class:
#Configuration
public class MyControllerTestConfiguration {
#Bean
public MyController myController() {
return new MyController();
}
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
}
And the error I get: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type [test.test.MyRepository] found for dependency
I tried to initialize the mock using Mockito's #InjectMocks annotation but this fails because the #PostConstruct method is called before the mocks injection, generating a NullPointerException.
And I cannot simply mock the repository because in real life that would make me mock A LOT of classes...
Can anyone help me on this?
Use constructor instead of field injection. That makes testing a lot easier.
#Service
public class MyService {
private final MyRepository repository;
#Autowired
public MyService(MyRepository repository) {
this.repository = repository;
}
public void whatever() {}
public void create() {
repository.save();
}
}
-
#Controller
public class MyController {
private final MyService service;
#Autowired
public MyController(MyService service) {
this.service = service;
}
#PostConstruct
public void init() {
service.whatever();
}
public void doSomething() {
service.create();
}
}
This has several advantages:
You don't need Spring in your tests. This allows you to do proper unit tests. It also makes the test incredibly fast (from seconds to milliseconds).
You cannot accidentally create an instance of a class without its dependencies which would result in a NullPointerException.
As #NamshubWriter pointed out:
[The instance fields for the dependencies] can be final, so 1) they cannot be accidentally modified, and 2) any thread reading the field will read the same value.
Discard the #Configuration class and write a test like this:
#RunWith(MockitoJUnitRunner.class)
public class MyControllerTest {
#Mock
private MyRepository repository;
#InjectMocks
private MyService service;
#Test
public void testDoSomething() {
MyController myController = new MyController(service);
myController.doSomething();
}
}
Use interfaces, especially if you use some kind of AOP (transactions, security, etc), i.e. you'll have interface MyService and class MyServiceImpl.
In configuration you'll have:
#Bean
public MyService myService() {
return Mockito.mock(MyService.class);
}
you should put the #InjectMocks annotation in your controller and #Mock in your service, look:
#Autowired
#InjectMocks
private MyController myController;
#Autowired
#Mock
private MyService myService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testDoSomething() {
myController.doSomething();
}

Mock services inside another spring service with mockito

I'm facing problems mocking services injected inside of other services within the Spring framework. Here is my code:
#Service("productService")
public class ProductServiceImpl implements ProductService {
#Autowired
private ClientService clientService;
public void doSomething(Long clientId) {
Client client = clientService.getById(clientId);
// do something
}
}
I want to mock the ClientService inside my test, so I tried the following:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(locations = { "classpath:/spring-config.xml" })
public class ProductServiceTest {
#Autowired
private ProductService productService;
#Mock
private ClientService clientService;
#Test
public void testDoSomething() throws Exception {
when(clientService.getById(anyLong()))
.thenReturn(this.generateClient());
/* when I call this method, I want the clientService
* inside productService to be the mock that one I mocked
* in this test, but instead, it is injecting the Spring
* proxy version of clientService, not my mock.. :(
*/
productService.doSomething(new Long(1));
}
#Before
public void beforeTests() throws Exception {
MockitoAnnotations.initMocks(this);
}
private Client generateClient() {
Client client = new Client();
client.setName("Foo");
return client;
}
}
The clientService inside productService is the Spring proxy version, not the mock that I want. Is it possible to do what I want with Mockito?
You need to annotate ProductService with #InjectMocks:
#Autowired
#InjectMocks
private ProductService productService;
This will inject the ClientService mock into your ProductService.
There are more ways to achieve this, the most easy way to do this will be don't use field injection, but setter injection which means you should have:
#Autowired
public void setClientService(ClientService clientService){...}
in your service class, then you can inject your mock to the service in test class:
#Before
public void setUp() throws Exception {
productService.setClientService(mock);
}
important: If this is only a unit test, please consider don't use SpringJUnit4ClassRunner.class, but MockitoJunitRunner.class, so that you can also use field inject for your fields.
In addition to
#Autowired
#InjectMocks
private ProductService productService;
Add the following method
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
I would like to suggest you annotate the Test target with #InjectMock
Currently
#Autowired
private ProductService productService;
#Mock
private ClientService clientService;
Change to
#InjectMock
private ProductService productService;
#Mock
private ClientService clientService;
incase you still have NullPointerException for the MockingService => you can use Mockito.any() as arguments.
Hopefully, it will help you.

Categories