2012-01-23 4 views
11

Finora le risposte di SO sono state assolutamente soddisfacenti per i miei problemi. Sto imparando i test unitari con Junit e Mockito e voglio testare la mia classe di servizio che fa parte della mia app web di Spring. Ho letto molti tutorial e articoli e ho ancora problemi a scrivere test di unità adeguati per il mio livello di servizio. Mi piacerebbe sapere le risposte per le mie domande, ma prima ho incollare del codice:Test dell'unità di servizio primaverile con utilizzo di mockito

La classe di servizio

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

mio incompiuta classe di test

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

} 

Domande :

  1. Qual è il modo migliore per eseguire test di unità in cui sono presenti metodi in metodi come nella mia classe di servizio? Posso testarli separatamente come sopra? [Ho diviso il mio codice in pochi metodi per avere un codice più pulito]
  2. È testRegisterNewAccount() buon test di unità per il mio metodo di servizio? Il test è verde, ma non ne sono sicuro.
  3. Ho riscontrato un errore nel mio testShouldSetRoleToAccount. Che cosa sto facendo di sbagliato?
  4. Come testare checkIfUsernameExists?

Forse qualcuno mi aiuterà con questo perché ho passato un paio di giorni e non ho fatto un progresso :(


UPDATE

classe di test finito

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

} 

risposta

5

Domanda 1 - Hai una coppia di opzioni qui.

Opzione 1: scrivere test separati per ciascun comportamento di ciascun metodo pubblico, in base a quanto richiesto per tale comportamento. Ciò mantiene ogni test pulito e separato, ma significa che la logica nei metodi secondari (come checkIfUsernameExists) verrà esercitata due volte. In un certo senso, si tratta di una duplicazione non necessaria, ma un vantaggio di questa opzione è che se si modifica l'implementazione, ma non il comportamento richiesto, si avranno comunque buoni test basati sul comportamento.

Opzione 2: utilizzare un Mockito Spy. Questo è un po 'come un mock, tranne che lo si crea da un oggetto reale, e il comportamento predefinito è che tutti i metodi vengono eseguiti come al solito. È quindi possibile spegnere e verificare i metodi secondari, al fine di testare i metodi che li chiamano.

Domanda 2 - Questo sembra un buon test per il caso "successo" di registerNewAccount. Si prega di riflettere su quali circostanze potrebbero causare l'errore registerNewAccount e restituire false; e prova questo caso.

Domanda 3 - Non ho dato una buona occhiata a questo; ma prova a correre con il debugger e scopri a che punto i tuoi oggetti differiscono da quello che ti aspetti. Se non riesci a risolverlo, post di nuovo e avrò un'altra occhiata.

Domanda 4 - Per testare il caso negativo, mina il tuo AccountDao per lanciare l'eccezione richiesta. Altrimenti, vedi le mie risposte alla domanda 1.

+0

Grazie David. Rispondi alla domanda 1 Capisco perfettamente e ho scelto l'opzione 1. Riguardo alla domanda 3 Sono riuscito a risolvere un problema. Ho dovuto sostituire l'account fittizio con l'account creato dal nuovo operatore ed era [sic!] :). Anche la domanda 4 mi ha aiutato, ma ho altri problemi. Come puoi vedere grazie ai tuoi suggerimenti sono riuscito a scrivere test per tutti i miei metodi. Funzionano bene tranne testShouldNotSetRoleToAccount e testShouldNotLoadUserByUsername. Entrambi falliscono quando c'è "previsto = ...". Senza di esso è ok. Inoltre, in primo luogo rende anche un errore e il test è Errore. Potresti aiutarmi? –

+0

Mi dispiace, mi ci è voluto un po 'per tornare da te. Se questi test falliscono con l'eccezione prevista, significa che l'eccezione non viene effettivamente generata. Sei sicuro che 'roleDao' e' accountDaoDao 'siano stati effettivamente impostati per i mock? Puoi controllare questo con il debugger. Inoltre, non stai usando 'anyString()' correttamente - questo è per lo stub e la verifica, non eseguendo il tuo metodo in realtà; Non so se questa sia la causa del tuo problema. Nelle linee in cui effettivamente esegui i tuoi metodi, metti il ​​valore effettivo che vuoi passare, invece di 'anyString()'. –

+0

Eh lol, sono stupido. Questo errore di cui stavo parlando è stato solo le informazioni della console dal registratore. Nel roleDao quando viene rilevata un'eccezione c'è logger.error (..): P. Ho guardato il mio codice di test a fondo e ora è tutto ok. "previsto" in testShouldNotSetRoleToAccount e testCheckIfUsernameExistsIsFalse non è necessario. Più tardi aggiornerò la mia lezione di test e potremo chiudere questa discussione. Grazie ancora una volta David, i tuoi suggerimenti sono stati molto utili :) –