Test services in Mockito - java

I want to test service
public class UserServiceTest {
#Autowired
private UserServiceImpl userService;
#Before
public void setup() {
MockitoAnnotations.initMocks(this);
}
#Test
public void testExistsUsername() {
User user = userService.findByUsername("jonki97").get();
}
}
However, during the test he throws me out
java.lang.NullPointerException
at com.service.service.UserServiceTest.testExistsUsername(UserServiceTest.java:32)
My service still contains repository
#Service("userService")
public class UserServiceImpl implements UserService {
private UserRepository userRepository;
#Autowired
public UserServiceImpl(UserRepository userRepository) {
this.userRepository = userRepository;
}
It may be that I missed something wrong and therefore throws null.

You do not have any mocks instantiated, even you have this method for initiating mocks. You need a mock of the repository you are calling in your test class:
#Mock
UserRepository userRepository;
And as well to provide the behavior of the mock (what to do when the method is called). Something like:
when(userRepository.findByUsername("jonki97")).thenReturn("OK");
// test the method after
Hope it helps

Have you tried using Runner
#RunWith(SpringRunner.class)
This works for me:
#RunWith(SpringRunner.class)
public class UserServiceTest {
#Mock
UserRepository userRepository;
#Before
public void setup(){
when(userRepository.findByUsername("jonki97")).thenReturn("jonki97");
}
#Test
public void testExistsUsername() {
String user = userRepository.findByUsername("jonki97");
assertEquals("jonki97", user);
}
}

Related

Junit Test Pass even when the method called inside may fail

I am creating a Junit Unit Test to check the createAccount method in service that calls Service a helper method. Please find it below.
Service class
public class AccountServiceImpl {
#Autowired
AccountHelper accountHelper;
#Override
public Account createAccount(Account account) throws CustomerNotFoundException {
accountHelper.checkAccountTypeForCustomer(account);
return accountRepository.save(account);
}
}
Helper Class:
public void checkAccountTypeForCustomer(Account acc) throws CustomerNotFoundException {
Boolean customerExists = customerRepository.existsById(acc.getCustomerId());
if(!customerExists) {
throw new CustomerNotFoundException("604", Message.CUSTOMER_NOT_FOUND);
}
}
AccountServiceTest class
#ExtendWith(MockitoExtension.class)
public class AccountServiceTest {
#Mock
private AccountRepository accountRepository;
#Mock
private CustomerRepository customerRepository;
#Mock
private AccountHelper accountHelper;
#InjectMocks
private AccountService testService;
#Test
void testCreateAccount() throws CustomerNotFoundException {
Account account = Account.builder().
accountType(AccountType.SAVINGS).
openingBalance(BigDecimal.valueOf(3000)).
ifsc("IFSC1").
customerId(1).
build();
testService.createAccount(account);
}
}
Above Test is passing although the customer is not present in the database.
The test is incomplete. But still the statement: testService.createAccount(account);
must fail as per my understanding.
Kindly correct me if I am wrong. I am relatively new to Junit.
However if I place the implementation for checkAccountTypeForCustomer() inside the service method instead of in the helper, the test case fails as expected.
The reason is that accountHelper is mocked in your test, which means that invocation of accountHelper.checkAccountTypeForCustomer(account) doesn't execute your business code.
I recommend you to use Spring mocking in this case, and to specify how your repository is expected to behave. It would look something like this:
#ExtendWith(SpringExtension.class)
class AccountServiceTest {
#MockBean
private CustomerRepository repository;
#Autowired
private AccountService testService;
#Test
void testCreateAccount() throws CustomerNotFoundException {
Mockito.when(repository.existsById(anyInt())).thenReturn(false);
...
CustomerNotFoundException thrown = Assertions.assertThrows(CustomerNotFoundException.class, () -> testService.createAccount(account));
Assertions.assertEquals("the exception message", thrown.getMessage());
}
}

Problem with password encoder in the test

When trying to test a method, I'm getting a NullPointerException in this line of code:
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
java.lang.NullPointerException: Cannot invoke "org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder.encode(java.lang.CharSequence)" because the return value of "com.example.paymybuddy.security.WebConfig.passwordEncoder()" is null
I have provided a mock of WebConfig class but it looks like still missing something. Thank you for your help in advance.
My code:
#ExtendWith(MockitoExtension.class)
public class Test {
#Mock
SecurityService securityService;
#Mock
IUserRepository userRepository;
#Mock
WebConfig webConfig;
#InjectMocks
UserService userService;
#Test
public void updateProfileTest() {
UserProfileDTO userDTO = new UserProfileDTO();
userDTO.setEmail("john#simons");
userDTO.setFirstName("John");
userDTO.setLastName("Simons");
userDTO.setPassword("pass");
userDTO.setConfirmPassword("pass");
User userForUpdate = new User();
userForUpdate.setFirstName("Joo");
userForUpdate.setLastName("Sim");
userForUpdate.setBalance(242.20);
userForUpdate.setEmail("john#simons");
userForUpdate.setPassword("pass");
userForUpdate.setRole("ROLE_USER");
User userUpdated = new User();
userUpdated.setFirstName("John");
userUpdated.setLastName("Simons");
userUpdated.setBalance(242.20);
userUpdated.setEmail("john#simons");
userUpdated.setPassword("pass");
userUpdated.setRole("ROLE_USER");
when(userRepository.findByEmail(any())).thenReturn(userForUpdate);
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
when(userRepository.save(any())).thenReturn(userUpdated);
User test = userService.updateProfile(userDTO);
assertEquals("john#simons",test.getEmail());
}
}
#Service
public class UserService {
#Autowired
private SecurityService securityService;
#Autowired
private IUserRepository userRepository;
#Autowired
private WebConfig webConfig;
public User updateProfile (UserProfileDTO userProfileDTO) {
User user = userRepository.findByEmail(securityService.getLoggedUser());
user.setFirstName(userProfileDTO.getFirstName());
user.setLastName(userProfileDTO.getLastName());
user.setEmail(securityService.getLoggedUser());
if(!userProfileDTO.getConfirmPassword().equals(userProfileDTO.getPassword())){
throw new PasswordException("Password confirmation not match");
}
user.setPassword(webConfig.passwordEncoder().encode(userProfileDTO.getPassword()));
return userRepository.save(user);
}
}
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Bean
public BCryptPasswordEncoder passwordEncoder() {
BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
return bCryptPasswordEncoder;
}
#Bean
RequestRejectedHandler requestRejectedHandler() {
return new HttpStatusRequestRejectedHandler();
}
}
when(webConfig.passwordEncoder().encode(any())).thenReturn(userUpdated.getPassword());
This line is the problem (or more broadly your design but more on that later). webConfig is a mock, which means it will intercept all method calls and excert the registered behavior or the default if none. webConfig.passwordEncoder() is the first call on yuor mock and you haven't told the mock what to do, hence it will do the default which is to return null. You go then on by mocking the encode call on the return value, which will fail.
You should either tell Mockito to do something else by default (#Mock(answer = Answers.RETURNS_DEEP_STUBS instead of plain #Mock) or explicitly mock the call to webConfig.passwordEncoder() and return a mocked PasswordEncoder and then mock behavior on that for encode.
However the fact that you inject the WebConfig and not a PasswordEncoder in your UserService is the actual problem (or the problem in your design). You should inject a PasswordEncoder.
#Service
public class UserService {
private final SecurityService securityService;
private final IUserRepository userRepository;
private final PasswordEncoder encoder;
public UserService(SecurityService securityService, IUserRepository userRepository, PasswordEncoder encoder) {
this.securityService=securityService;
this.userRepository=userRepository;
this.encoder=encoder;
}
public User updateProfile (UserProfileDTO userProfileDTO) {
User user = userRepository.findByEmail(securityService.getLoggedUser());
user.setFirstName(userProfileDTO.getFirstName());
user.setLastName(userProfileDTO.getLastName());
user.setEmail(securityService.getLoggedUser());
if(!userProfileDTO.getConfirmPassword().equals(userProfileDTO.getPassword())){
throw new PasswordException("Password confirmation not match");
}
user.setPassword(encoder.encode(userProfileDTO.getPassword()));
return userRepository.save(user);
}
}
Now you can modify your test as well.
#ExtendWith(MockitoExtension.class)
public class Test {
#Mock
SecurityService securityService;
#Mock
IUserRepository userRepository;
#Mock
PasswordEncoder encoder;
#InjectMocks
UserService userService;
#Test
public void updateProfileTest() {
UserProfileDTO userDTO = new UserProfileDTO();
userDTO.setEmail("john#simons");
userDTO.setFirstName("John");
userDTO.setLastName("Simons");
userDTO.setPassword("pass");
userDTO.setConfirmPassword("pass");
User userForUpdate = new User();
userForUpdate.setFirstName("Joo");
userForUpdate.setLastName("Sim");
userForUpdate.setBalance(242.20);
userForUpdate.setEmail("john#simons");
userForUpdate.setPassword("pass");
userForUpdate.setRole("ROLE_USER");
User userUpdated = new User();
userUpdated.setFirstName("John");
userUpdated.setLastName("Simons");
userUpdated.setBalance(242.20);
userUpdated.setEmail("john#simons");
userUpdated.setPassword("pass");
userUpdated.setRole("ROLE_USER");
when(userRepository.findByEmail(any())).thenReturn(userForUpdate);
when(encoder.encode(any())).thenReturn(userUpdated.getPassword());
when(userRepository.save(any())).thenReturn(userUpdated);
User test = userService.updateProfile(userDTO);
assertEquals("john#simons",test.getEmail());
}
}
Now your service just depends on the classes it needs instead of knowing something about the configuration.

Should I instantiate my Service in order to share infornation between different methods?

Currently I have an endpoint in my controller that calls different methods from my service. Each of these methods recieve a parameter called executionId that is different for every http request.
#RestController
public class ProcedureController {
#Autowired
MyService service;
#GetMapping
public void execute(){
String executionId = ...; //randomly generated
service.executeMethodA(executionId);
service.executeMethodB(executionId);
}
}
#Service
public class MyService {
public void executeMethodA(String executionId){
...
}
public void executeMethodB(String executionId){
...
}
}
It works fine but it seems very repetitive to pass executionId as a parameter to each method. So one solution that I thought was to instantiate my service with the executionId so it can hold this information and every method can access it.
#RestController
public class ProcedureController {
#GetMapping
public void execute(){
String executionId = ...; //randomly generated
MyService service = new MyService(executionId);
service.executeMethodA();
service.executeMethodB();
}
}
#Service
public class MyService {
String executionId;
public MyService(String executionId){
this.executionId = executionId;
}
public void executeMethodA(){
// Use executionId here
...
}
public void executeMethodB(){
// Use executionId here
...
}
}
Is this solution a bad practice? If so, how can I achieve something similar?
Spring allows you to inject a managed object (bean) as a dependency into another object via the #Autowired annotation.
For example, if I have a UserService that has a dependency on UserRepository, I can have the UserRepository injected using #Autowired annotation like this:
UserRepository Class
class UserRepository {
UserRepository () {}
}
UserService Class
class UserService {
#Autowired
private UserRepository userRepository;
UserService () {}
}
This is done using Field Injection. The same thing can be accomplice using Setter Injection:
class UserService {
private UserRepository userRepository;
UserService () {}
#Autowired // Using setter injection
public void setUserRepository(
UserRepository userRepository) {
this.userRepository = userRepository
}
}
or via Constructor Injection:
class UserService {
private UserRepository userRepository;
#Autowired // Using constructor Injection
UserService (UserRepository userRepository) {
this.userRepository = userRepository
}
}

Can't pass mock to the inject mock service

I have service:
#Service
class UserService {
private final Map<AbstractSomeService, CustomEnum> someMap;
public UserService(List<AbstractSomeService> someService) {
someService.forEach(service -> someMap.put(service.getCustomEnum(), service));
}
public void logicExecution() {
//code
}
}
When i am mocking as below: i am getting NullPointer:
#Mock
private SomeService someService; // Service which is extended of AbstractSomeService
#InjectMocks
private UserService userService = new UserService(Collection.singletonList(someService))
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
#Test
public void testBrokenJunit() {
userService.logicExecution(); // NULL POINTER HERE (
}
SomeService:
#Service
public class SomeService extends AbstactSomeService() {
public CustomEnum getCustomEnum() {
return CustomEnum.BROKEN_JUNIT_TEST;
}
//logic here
}
Stack trace is quite simple:
java.lang.NullPointerException: Cannot invoke "getCustomEnum()" because "service" is null
StackTrace without constructor initialization:
org.mockito.exceptions.misusing.InjectMocksException:
Cannot instantiate #InjectMocks field named 'UserService' of type '...'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However the constructor or the initialization block threw an exception : Cannot invoke "java.util.List.forEach(java.util.function.Consumer)" because "someService" is null
at
Caused by: java.lang.NullPointerException: Cannot invoke "java.util.List.forEach(java.util.function.Consumer)" because "someService" is null
at UserService.<init>
P.S.
When i am using real object of UserService, not a mock everything is ok.
But it doesn't work with #InjectMocks
Try the following in your test class (you don't need to initialize UserService with a new instance via the constructor, #InjectMocks will do that for you):
#Mock
private SomeService someService;
#InjectMocks
private UserService userService;
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
}
#Test
public void testBrokenJunit() {
userService.logicExecution();
}
If this does not work because you have a List and not a simple SomeService you can do the following:
#Mock
private SomeService someService;
private UserService userService;
#BeforeEach
void setUp() {
MockitoAnnotations.openMocks(this);
userService = new UserService(Collection.singletonList(someService));
}
#Test
public void testBrokenJunit() {
userService.logicExecution();
}
Use #MockBean to create mock objects
import org.springframework.boot.test.mock.mockito.MockBean;
#MockBean
private User user;

spring controller test by mock service

Love Spring Testing Even More With Mocking and Unit Test Assistant:
A mocked service replaces multiple dependencies
enter image description here
#Controller
#RequestMapping("/people")
public class PeopleController {
#Autowired
protected PersonService personService;
#GetMapping
public ModelAndView people(Model model) {
for (Person person: personService.getAllPeople()) {
model.addAttribute(person.getName(), person.getAge());
}
return new ModelAndView("people.jsp", model.asMap());
}
}
private MockMvc mockMvc:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class PeopleControllerTest {
#Autowired
PersonService personService;
private MockMvc mockMvc;
#Configuration
static class Config {
// Other beans
#Bean
public PersonService getPersonService() {
return mock(PersonService.class);
}
}
#Test
public void testPeople() throws Exception {
// When
ResultActions actions = mockMvc.perform(get("/people"));
}
}
I get a mistake when I want to run mockMvc
java.lang.NullPointerException
Perform the following steps:
create service mock instead of service original
("PersonServiceMock")
replace service original by service mock
#Autowired
PersonService personService;
#Autowired
PeopleController peopleController;
private MockMvc mockMvc;
#Before
public void setup() {
peopleController = new PeopleController(new personServiceMock());
mvc = MockMvcBuilders.standaloneSetup(peopleController).build();
}
#Configuration
static class Config {
// Other beans
#Bean
public PersonService getPersonService() {
return mock(PersonService.class);
}
}
#Test
public void testPeople() throws Exception {
// When
ResultActions actions = mockMvc.perform(get("/people"));
}
}
That's because you are never initialising mockMvc in your code and the point where you access it results in nullPointerException. You need to initialise it before using it, and since multiple tests in your class could be using it, best place to do it is setup() method annotated with #before. Try below:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class PeopleControllerTest {
#Autowired
PersonService personService;
#Autowired
private WebApplicationContext wac;
private MockMvc mockMvc;
#Before
public void setup() {
mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
#Configuration
static class Config {
// Other beans
#Bean
public PersonService getPersonService() {
return mock(PersonService.class);
}
}
#Test
public void testPeople() throws Exception {
// When
ResultActions actions = mockMvc.perform(get("/people"));
}
}
from the source code I see that the mockMvc doesn't have any value, thats why it hits "java.lang.NullPointerException" for this line of code :
ResultActions actions = mockMvc.perform(get("/people"));
to make it run, I think need to give value to mockMvc first.
by constructor :
#Test
public void testPeople() throws Exception {
mockMvc = new MockMvc();
// When
ResultActions actions = mockMvc.perform(get("/people"));
}
or Autowired :
#Autowired
MockMvc mockMvc
depends on the purpose of MockMvc Class

Categories

Resources