Spring тестирование сервисного модуля с использованием mockito

До сих пор ответы от SO были полностью удовлетворительными для моих проблем. Я изучаю модульное тестирование с помощью Junit и Mockito, и я хочу проверить свой класс сервиса, который является частью моего веб-приложения Spring. Я прочитал много учебников и статей, и у меня все еще есть проблемы с написанием правильных модульных тестов для моего уровня обслуживания. Я хотел бы знать ответы на мои вопросы, но сначала вставляю код:

Класс обслуживания

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!");
    }
}
}

Мой незавершенный тестовый класс

@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)); 
}

}

Вопросы

  • Каков наилучший способ сделать модульные тесты, когда у меня есть методы в методах, подобных моему классу обслуживания? Могу ли я тестировать их отдельно, как указано выше? [Я разделил свой код на несколько методов, чтобы иметь более чистый код]
  • Является ли testRegisterNewAccount() хорошим unit test для моего метода обслуживания? Тест зеленый, но я не уверен в этом.
  • Я получаю сбой в своем testShouldSetRoleToAccount. Что я делаю неправильно?
  • Как протестировать checkIfUsernameExists?

Возможно, кто-то поможет мне в этом, потому что я провел пару дней, и я не добился прогресса: (


UPDATE

Готовый тестовый класс

@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());
}

}

Ответ 1

Вопрос 1. У вас есть пара вариантов.

Вариант 1 - напишите отдельные тесты для каждого поведения каждого публичного метода на основе того, что требуется для этого поведения. Это позволяет каждому анализу быть чистым и отдельным, но это означает, что логика вторичных методов (например, checkIfUsernameExists) будет выполняться дважды. В некотором смысле это ненужное дублирование, но одним из преимуществ этого параметра является то, что если вы измените реализацию, но не обязательно, вы по-прежнему будете иметь хорошие тесты, основанные на поведении.

Вариант 2 - используйте Mockito Spy. Это немного похоже на макет, за исключением того, что вы создаете его из реального объекта, и по умолчанию его поведение заключается в том, что все методы выполняются как обычно. Затем вы можете вырезать и проверить вторичные методы, чтобы проверить методы, которые их называют.

Вопрос 2 - Это похоже на хороший тест для случая успеха registerNewAccount. Пожалуйста, подумайте о том, какие обстоятельства могут привести к ошибке registerNewAccount и возврату false; и проверьте этот случай.

Вопрос 3 - Я не очень хорошо смотрел на это; но попробуйте запустить с помощью отладчика и узнайте, в какой момент ваши объекты отличаются от ожидаемых. Если вы не можете это исправить, отправьте сообщение еще раз, и у меня будет другой взгляд.

Вопрос 4 - Чтобы проверить отрицательный случай, заглушите свой макет AccountDao, чтобы выбросить необходимое исключение. В противном случае см. Ответы на вопрос 1.