Until now answers from SO has been utterly satisfying for my problems. I'm learning unit testing with Junit and Mockito and I want to test my service class which is a part of my Spring web app. I read many tutorials and articles and I still have problems to write proper unit tests for my service layer. I would like to know answers for my questions, but first I paste some code:
Service class
public class AccountServiceImpl implements AccountService {
#Autowired
AccountDao accountDao, RoleDao roleDao, PasswordEncoder passwordEncoder, SaltSource saltSource;
#PersistenceContext
EntityManager entityManager;
public Boolean registerNewAccount(Account newAccount) {
entityManager.persist(newAccount);
newAccount.setPassword(passwordEncoder.encodePassword(newAccount.getPassword(), saltSource.getSalt(newAccount)));
setRoleToAccount("ROLE_REGISTERED", newAccount);
return checkIfUsernameExists(newAccount.getUsername());
}
public void setRoleToAccount(String roleName, Account account) {
List<Role> roles = new ArrayList<Role>();
try {
roles.add(roleDao.findRole(roleName));
} catch(RoleNotFoundException rnf) {
logger.error(rnf.getMessage());
}
account.setRoles(roles);
}
public Boolean checkIfUsernameExists(String username) {
try {
loadUserByUsername(username);
} catch(UsernameNotFoundException unf) {
return false;
}
return true;
}
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
try {
Account loadedAccount = accountDao.findUsername(username);
return loadedAccount;
} catch (UserNotFoundException e) {
throw new UsernameNotFoundException("User: " + username + "not found!");
}
}
}
My unfinished test class
#RunWith(MockitoJUnitRunner.class)
public class AccountServiceImplTest {
private AccountServiceImpl accountServiceImpl;
#Mock private Account newAccount;
#Mock private PasswordEncoder passwordEncoder;
#Mock private SaltSource saltSource;
#Mock private EntityManager entityManager;
#Mock private AccountDao accountDao;
#Mock private RoleDao roleDao;
#Before
public void init() {
MockitoAnnotations.initMocks(this);
accountServiceImpl = new AccountServiceImpl();
ReflectionTestUtils.setField(accountServiceImpl, "entityManager", entityManager);
ReflectionTestUtils.setField(accountServiceImpl, "passwordEncoder", passwordEncoder);
ReflectionTestUtils.setField(accountServiceImpl, "saltSource", saltSource);
ReflectionTestUtils.setField(accountServiceImpl, "accountDao", accountDao);
ReflectionTestUtils.setField(accountServiceImpl, "roleDao", roleDao);
}
#Test
public void testRegisterNewAccount() {
Boolean isAccountCreatedSuccessfully = accountServiceImpl.registerNewAccount(newAccount);
verify(entityManager).persist(newAccount);
verify(newAccount).setPassword(passwordEncoder.encodePassword(newAccount.getPassword(), saltSource.getSalt(newAccount)));
assertTrue(isAccountCreatedSuccessfully);
}
#Test
public void testShouldSetRoleToAccount() throws RoleNotFoundException{
Role role = new Role(); //Maybe I can use mock here?
role.setName("ROLE_REGISTERED");
when(roleDao.findRole("ROLE_REGISTERED")).thenReturn(role);
accountServiceImpl.setRoleToAccount("ROLE_REGISTERED", newAccount);
assertTrue(newAccount.getRoles().contains(role));
}
}
Questions:
What is the best way to make unit tests where I have methods in methods like in my service class? Can I test them separately like above? [I divided my code into few methods to have cleaner code]
Is testRegisterNewAccount() good unit test for my service method? Test is green however I am not sure about it.
I am getting failure in my testShouldSetRoleToAccount. What am I doing wrong?
How to test checkIfUsernameExists?
Maybe someone will help me with this because I spent a couple of days and I didn't make a progress :(
UPDATE
Finished test class
#RunWith(MockitoJUnitRunner.class)
public class AccountServiceImplTest extends BaseTest {
private AccountServiceImpl accountServiceImpl;
private Role role;
private Account account;
#Mock private Account newAccount;
#Mock private PasswordEncoder passwordEncoder;
#Mock private SaltSource saltSource;
#Mock private EntityManager entityManager;
#Mock private AccountDao accountDao;
#Mock private RoleDao roleDao;
#Before
public void init() {
accountServiceImpl = new AccountServiceImpl();
role = new Role();
account = new Account();
ReflectionTestUtils.setField(accountServiceImpl, "entityManager", entityManager);
ReflectionTestUtils.setField(accountServiceImpl, "passwordEncoder", passwordEncoder);
ReflectionTestUtils.setField(accountServiceImpl, "saltSource", saltSource);
ReflectionTestUtils.setField(accountServiceImpl, "accountDao", accountDao);
ReflectionTestUtils.setField(accountServiceImpl, "roleDao", roleDao);
}
#Test
public void testShouldRegisterNewAccount() {
Boolean isAccountCreatedSuccessfully = accountServiceImpl.registerNewAccount(newAccount);
verify(entityManager).persist(newAccount);
verify(newAccount).setPassword(passwordEncoder.encodePassword(newAccount.getPassword(), saltSource.getSalt(newAccount)));
assertTrue(isAccountCreatedSuccessfully);
}
#Test(expected = IllegalArgumentException.class)
public void testShouldNotRegisterNewAccount() {
doThrow(new IllegalArgumentException()).when(entityManager).persist(account);
accountServiceImpl.registerNewAccount(account);
}
#Test
public void testShouldSetRoleToAccount() throws RoleNotFoundException {
when(roleDao.findRole(anyString())).thenReturn(role);
accountServiceImpl.setRoleToAccount("ROLE_REGISTERED", account);
assertTrue(account.getRoles().contains(role));
}
#Test
public void testShouldNotSetRoleToAccount() throws RoleNotFoundException {
when(roleDao.findRole(anyString())).thenThrow(new RoleNotFoundException());
accountServiceImpl.setRoleToAccount("ROLE_RANDOM", account);
assertFalse(account.getRoles().contains(role));
}
#Test
public void testCheckIfUsernameExistsIsTrue() throws UserNotFoundException {
when(accountDao.findUsername(anyString())).thenReturn(account);
Boolean userExists = accountServiceImpl.checkIfUsernameExists(anyString());
assertTrue(userExists);
}
#Test
public void testCheckIfUsernameExistsIsFalse() throws UserNotFoundException {
when(accountDao.findUsername(anyString())).thenThrow(new UserNotFoundException());
Boolean userExists = accountServiceImpl.checkIfUsernameExists(anyString());
assertFalse(userExists);
}
#Test
public void testShouldLoadUserByUsername() throws UserNotFoundException {
when(accountDao.findUsername(anyString())).thenReturn(account);
Account foundAccount = (Account) accountServiceImpl.loadUserByUsername(anyString());
assertEquals(account, foundAccount);
}
#Test(expected = UsernameNotFoundException.class)
public void testShouldNotLoadUserByUsername() throws UserNotFoundException {
when(accountDao.findUsername(anyString())).thenThrow(new UsernameNotFoundException(null));
accountServiceImpl.loadUserByUsername(anyString());
}
}
Question 1 - You've got a couple of options here.
Option 1 - write separate tests for each behaviour of each public method, based on what is required for that behaviour. This keeps each test clean and separate, but it does mean that the logic in the secondary methods (such as checkIfUsernameExists) will be exercised twice. In a sense, this is unnecessary duplication, but one advantage of this option is that if you change the implementation, but not the required behaviour, you'll still have good tests based on the behaviour.
Option 2 - use a Mockito Spy. This is a little like a mock, except that you create it from a real object, and the default behaviour of it is that all the methods run as usual. You can then stub out and verify the secondary methods, in order to test the methods that call them.
Question 2 - This looks like a good test for the "success" case of registerNewAccount. Please think about what circumstances would cause registerNewAccount to fail and return false; and test this case.
Question 3 - I haven't had a good look at this; but try running with the debugger, and find out at which point your objects differ from what you expect. If you can't work it out, post again and I'll have another look.
Question 4 - To test the negative case, stub your mock of the AccountDao to throw the required exception. Otherwise, see my answers for question 1.
Related
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());
}
}
I am trying to understand Mockito and Junit 4. I hava an interface named UserService which has a method named getUserInfo. I have written an implementation class UserServiceImpl to this interface.
I am using this getUserInfo method inside another class named UserInfoImpl. Now I am trying to Junit UserInfoImpl by Mocking UserServiceImpl. But instead of going to to the mock method, the call is going to the actual method. I have verified it by adding sysout to the actual method. I am not sure why it is not getting mocked. Can someone please throw some light.
Junit Class
public class Junit4WithMockito {
private UserInfoImpl userInfo;
#Mock
private UserService userService;
#Before
public void setUp() {
System.out.println("Inside Setup");
MockitoAnnotations.initMocks(this);
}
#Test
public void testUserInfoImpl() throws InterruptedException {
String cid="yu444";
userInfo = new UserInfoImpl();
userInfo.setUserService(userService);
when(userService.getUserInfo(cid)).thenReturn(new User("John Doe",33));
Assert.assertEquals("Peter",userInfo.getUserInfo(cid).getUsername());
}
}
UserInfoImpl Class
public class UserInfoImpl implements UserInfo {
private UserService userService;
public UserService getUserService() {
return userService;
}
public void setUserService(UserService userService) {
this.userService = userService;
}
#Override
public User getUserInfo(String username) {
try {
userService = new UserServiceImpl();
return userService.getUserInfo(username);
} catch (InterruptedException e) {
e.printStackTrace();
return null;
}
}
}
****** UserServiceImpl Class which I am trying to mock *********
public class UserServiceImpl implements UserService {
#Override
public User getUserInfo(String username) throws InterruptedException {
System.out.println("Inside the actual Service");
Thread.sleep(5000);
return new User("John Doe",33);
}
}
Error Message Below
You don't usually want to mock an implementation class, especially when there is an interface for it. The point of mocking is that you don't need (or want) the real implementation to be used.
So in your case, your #Mock should be
#Mock
private UserService userService;
Furthermore, you have this line in UserInfoImpl:
userService = new UserServiceImpl();
Obviously this replaces whatever instance of UserService you inject (including the mock). I'm guessing that's a mistake (maybe left around from an earlier attempt) since I can't think of any reason you'd really want to instantiate something that you're injecting.
I want to test a few cases in a method by mocking external dependency to return different results for every test case. But when always returns what is defined at first time (in this example - empty set) and that brokes the next tests.
If I run tests one by one they pass successfully but when I run the whole class only the first test pass and others fail.
Testing class:
class ExampleTest {
#Mock
private Dao dao;
#Mock
private Validator validator;
#Spy
#InjectMocks
Controller controller;
#BeforeEach
void setUp() {
initMocks(this);
}
private final static Set DATA = Set.of("data1", "data2");
#Test
void firstTest() throws UserDashboardException, DashboardException, WidgetException {
when(validator.filter(DATA)).thenReturn(Collections.emptySet());
assertThrows(Exception.class, () -> controller.create(DATA));
}
#Test
void secondTest() throws UserDashboardException, DashboardException, WidgetException {
when(validator.filter(DATA)).thenReturn(DATA);
controller.create(DATA);
verify(dao, times(1)).create(eq(DATA));
}
}
Tested class:
public class Controller {
private Dao dao;
private Validator validator;
public Controller(Dao dao,Validator validator) {
this.dao = dao;
this.validator = validator;
}
public String create(Set<String> data) {
data = validator.filter(data);
if (data.isEmpty()) {
throw new Exception("Invalid data.");
}
return dao.create(data);
}
}
So, in both tests create method throws an exception which is not what I expect. Maybe I miss some point?
Have you tried with doReturn method?
doReturn(DATA).when(validator).filter(DATA)
which can be import from org.mockito.Mockito.doReturn;
Edited: there might be a bug inside your code implementation:
data = validator.filter(data);
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.
I am having an issue with stubbing my repository. I was suggested to just create another application.properties (which I have not done) and to use an in-memory database like H2. I was wondering though if I can just stub the call so when myDataService.findById(id) is called instead of it attempting to get that from the database just a mocked object can be returned?
I am new to writing mocks for my unit tests and spring boot in general so maybe I am missing something. Code below (tried to simplify and made names generic for posting here).
My test class
public class MyServiceImplTest
{
private MyDataService myDataService;
private NyService myService;
private MyRepository myRepository;
#Before
public void setUp() {
myDataService = Mockito.mock(MyDataServiceImpl.class);
myService = new MyServiceImpl(myDataService);
}
#Test
public void getById_ValidId() {
doReturn(MyMockData.getMyObject()).when(myDataService).findById("1");
when(myService.getById("1")).thenReturn(MyMockData.getMyObject());
MyObject myObject = myService.getById("1");
//Whatever asserts need to be done on the object myObject
}
}
Class used for making the service call to the data layer
#Service
public class MyServiceImpl implements MyService {
MyDataService myDataService;
#Autowired
public MyServiceImpl(MyDataService myDataService) {
this.myDataService = myDataService;
}
#Override
public MyObject getById(String id) {
if(id == null || id == "") {
throw new InvalidRequestException("Invalid Identifier");
}
MyObject myObj;
try {
myObj = myDataService.findById(id);
}catch(Exception ex) {
throw new SystemException("Internal Server Error");
}
return myObj;
}
}
This is where I am having the issue in my test. When the findById() method is called, the variable repository is null so when trying to do repository.findOne(id) it throws an exceptionn. This is what I am attempting to mock, but the repository is giving me issues.
#Repository
#Qualifier("MyRepo")
public class MyDataServiceImpl {
#PersistenceContext
private EntityManager em;
private MyRepository repository;
#Autowired
public MyDataServiceImpl(MyRepository repository) {
super(repository);
this.repository = repository;
}
public MyObject findById(String id) {
P persitentObject = repository.findOne(id);
//Calls to map what persitentObject holds to MyObject and returns a MyObject
}
}
Code for MyRepository here just to show it's an empty interface that extends CrudRepository
public interface MyRepository extends CrudRepository<MyObjectPO, String>, JpaSpecificationExecutor<MyObjectPO> {
}
Let me begin by saying you are on the right track by using Constructor Injection and not Field Injection(which makes writing tests with mocks much simpler).
public class MyServiceImplTest
{
private MyDataService myDataService;
private NyService myService;
#Mock
private MyRepository myRepository;
#Before
public void setUp() {
MockitoAnnotations.initMocks(this); // this is needed for inititalizytion of mocks, if you use #Mock
myDataService = new MyDataServiceImpl(myRepository);
myService = new MyServiceImpl(myDataService);
}
#Test
public void getById_ValidId() {
doReturn(someMockData).when(myRepository).findOne("1");
MyObject myObject = myService.getById("1");
//Whatever asserts need to be done on the object myObject
}
}
The call goes all the way from your service --> dataService. But only your repository calls are mocked.
This way you can control and test all the other parts of your classes(both service and dataService) and mock only repository calls.