2012-01-23 128 views
11

到目前爲止,來自SO的答案已經完全滿足我的問題。我正在學習Junit和Mockito的單元測試,我想測試我的服務類,這是我的Spring Web應用程序的一部分。我閱讀了許多教程和文章,但仍然遇到問題需要爲我的服務層編寫適當的單元測試。我想知道我的問題的答案,但首先我粘貼一些代碼:使用mockito進行彈簧服務單元測試

服務類

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

} 

問題

  1. 在我的服務類中有方法的情況下進行單元測試的最佳方式是什麼?我可以像上面那樣分別測試它們嗎? [我把我的代碼分成幾種方法來得到更清晰的代碼]
  2. TestRegisterNewAccount()對我的服務方法來說是很好的單元測試嗎?測試是綠色的,但我不確定。
  3. 我在我的testShouldSetRoleToAccount失敗。我究竟做錯了什麼?
  4. 如何測試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()); 
} 

} 

回答

5

問題1 - 你有一對夫婦的選擇。

選項1 - 根據該行爲的要求,爲每個公共方法的每個行爲分別編寫測試。這樣可以保持每個測試的清潔和分離,但這確實意味着次要方法(例如checkIfUsernameExists)中的邏輯將被執行兩次。從某種意義上說,這是不必要的重複,但是這個選項的一個優點是,如果你改變了實現,但不是所需的行爲,你仍然會有基於行爲的良好測試。

選項2 - 使用Mockito間諜。這有點像一個模擬,除了你從一個真實的對象創建它,它的默認行爲是所有的方法都照常運行。然後,您可以將其刪除並驗證次要方法,以測試調用它們的方法。

問題2 - 對於registerNewAccount的「成功」案例來說,這看起來很不錯。請考慮在什麼情況下會導致registerNewAccount失敗並返回false;並測試這種情況。

問題3 - 我沒有看清楚這個;但試着用調試器運行,並找出你的對象在哪一點上與你期望的不同。如果你不能解決問題,請再次發帖,我會再看一次。

問題4 - 要測試負的情況下,你的存根的AccountDao的模擬拋出所需的異常。否則,請參閱問題1

+0

感謝大衛·我的答案。回答問題1我完全理解,我選擇了選項1.關於問題3我設法解決了一個問題。我不得不用新操作員創建的帳戶替換模擬帳戶,這是[原創!] :)。問題4也幫助了我,但我還有其他問題。正如你可以看到感謝你的提示,我設法爲我的所有方法編寫測試。他們的工作除了testShouldNotSetRoleToAccount和testShouldNotLoadUserByUsername確定。當有「預期= ...」時,兩者都失敗。沒有它就沒關係。另外首先還會使en錯誤和測試是錯誤。你可以幫幫我嗎? –

+0

對不起,我花了一段時間回到你身邊。如果這些測試失敗且預期的異常設置,則意味着該異常實際上不會被拋出。你確定'roleDao'和'accountDao'實際上已經設置爲mock嗎?你可以用調試器來檢查。另外,你沒有使用'anyString()'正確的 - 這是磕碰和驗證,實際上沒有運行你的方法;我不知道這是否是你的問題的原因。在實際運行你的方法行,把要傳遞的實際值,而不是'anyString()'。 –

+0

呃哈哈,我很笨。我剛纔談到的這個錯誤只是來自記錄器的控制檯信息。在發生異常時,在roleDao中有logger.error(..):P。我徹底查看了我的測試代碼,現在一切正常。 「預計」在testShouldNotSetRoleToAccount並且不需要testCheckIfUsernameExistsIsFalse。稍後我會更新我的測試課程,我們可以結束這個討論。再次感謝大衛,你的提示非常有幫助:) –