Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Spring service unit testing using mockito

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:

  1. 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]
  2. Is testRegisterNewAccount() good unit test for my service method? Test is green however I am not sure about it.
  3. I am getting failure in my testShouldSetRoleToAccount. What am I doing wrong?
  4. 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());
}

}
like image 527
Mariusz Grodek Avatar asked Jan 23 '12 22:01

Mariusz Grodek


1 Answers

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.

like image 141
Dawood ibn Kareem Avatar answered Sep 16 '22 18:09

Dawood ibn Kareem