2016-07-11 16 views
10
Android Studio 2.1.2 

Tôi muốn kiểm tra xem callbacks onUsernameError, onPasswordError và onSuccess, trong LoginModelImp có thực sự được gọi hay không. Tôi không chắc chắn cách kiểm tra trình xử lý sự kiện. Tuy nhiên, kiểm tra không thành công vì những hàm này không bao giờ được gọi. Tôi chế giễu chúng với mockito và cố gắng xác minh chúng.Kiểm tra đơn vị MVP bằng cách sử dụng mockito với trình nghe sự kiện

Đây là mã của tôi cho đến thời điểm này.

giao diện trình bày

public interface LoginPresenterContract<LoginFragmentViewContract> { 
    void validateCredentials(); 

    void attachView(LoginFragmentViewContract view); 
    void detachView(); 
} 

thực hiện Presenter

public class LoginPresenterImp implements LoginPresenterContract<LoginFragmentViewContract>, LoginModelContract.OnLoginCompletedListener { 

    private LoginModelContract mLoginModelContract; 
    private LoginFragmentViewContract mLoginFragmentView; 

    public LoginPresenterImp(LoginModelContract loginModelContract) { 
     mLoginModelContract = loginModelContract; 
    } 

    /* 
    * LoginPresenterContact - implementation 
    */ 
    @Override 
    public void attachView(LoginFragmentViewContract view) { 
     mLoginFragmentView = view; 
    } 

    @Override 
    public void detachView() { 
     mLoginFragmentView = null; 
    } 

    @Override 
    public void validateCredentials() { 
     if(mLoginModelContract != null) { 
      mLoginModelContract.login(
        mLoginFragmentView.getUsername(), 
        mLoginFragmentView.getPassword(), 
        LoginPresenterImp.this); 
     } 
    } 

    /* 
    * LoginModelContract.OnLoginCompletedListener - implementation 
    */ 
    @Override 
    public void onUsernameError() { 
     if(mLoginFragmentView != null) { 
      mLoginFragmentView.onLoginFailed("Incorrect username"); 
     } 
    } 

    @Override 
    public void onPasswordError() { 
     if(mLoginFragmentView != null) { 
      mLoginFragmentView.onLoginFailed("Incorrect password"); 
     } 
    } 

    @Override 
    public void onSuccess() { 
     if(mLoginFragmentView != null) { 
      mLoginFragmentView.onLoginSuccess(); 
     } 
    } 
} 

giao diện mẫu

public interface LoginModelContract { 
    interface OnLoginCompletedListener { 
     void onUsernameError(); 
     void onPasswordError(); 
     void onSuccess(); 
    } 
    void login(String username, String password, OnLoginCompletedListener onLoginCompletedListener); 
} 

mẫu thực hiện

public class LoginModelImp implements LoginModelContract { 
    /* Testing Valid username and passwords */ 
    private static String validUsername = "steve"; 
    private static String validPassword = "1234"; 

    @Override 
    public void login(final String username, 
         final String password, 
         final OnLoginCompletedListener onLoginCompletedListener) { 

     boolean hasSuccess = true; 
     if(TextUtils.isEmpty(username) || !username.equals(validUsername)) { 
     /* TEST onUsernameError() */ 
      onLoginCompletedListener.onUsernameError(); 
      hasSuccess = false; 
     } 

     if(TextUtils.isEmpty(password) || !password.equals(validPassword)) { 
     /* TEST onPasswordError() */ 
      onLoginCompletedListener.onPasswordError(); 
      hasSuccess = false; 
     } 

     if(hasSuccess) { 
     /* TEST onSuccess() */ 
      onLoginCompletedListener.onSuccess(); 
     } 
    } 
} 

kiểm tra Junit4 với Mockito

public class LoginPresenterImpTest { 
    private LoginFragmentViewContract mMockViewContract; 
    private LoginModelContract mMockModelContract; 
    private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; 
    private LoginPresenterContract<LoginFragmentViewContract> mLoginPresenterContract; 

    @Before 
    public void setUp() throws Exception { 
     mMockViewContract = Mockito.mock(LoginFragmentViewContract.class); 
     mMockModelContract = Mockito.mock(LoginModelContract.class); 
     mMockOnLoginCompletedListener = Mockito.mock(LoginModelContract.OnLoginCompletedListener.class); 
     mLoginPresenterContract = new LoginPresenterImp(mMockModelContract); 
     mLoginPresenterContract.attachView(mMockViewContract); 
    } 

    @Test 
    public void shouldSuccessWithValidCredentials() { 
     when(mMockViewContract.getUsername()).thenReturn("steve"); 
     when(mMockViewContract.getPassword()).thenReturn("1234"); 

     mLoginPresenterContract.validateCredentials(); 

     verify(mMockViewContract, times(1)).getUsername(); 
     verify(mMockViewContract, times(1)).getPassword(); 

     verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); 

     verify(mMockOnLoginCompletedListener, never()).onPasswordError(); 
     verify(mMockOnLoginCompletedListener, never()).onUsernameError(); 
    } 
} 

Có cách nào để kiểm tra thực hiện điều này?

Rất cám ơn mọi đề xuất,

Trả lời

4

lớp thử nghiệm Các LoginPresenterImpTest là về sự thử thách của LoginPresenterImp lớp, và nó chỉ nên sử dụng thực hiện thực tế của nó và mocks cộng tác viên của mình. Lớp LoginModelContract.OnLoginCompletedListener là một cộng tác viên của LoginModelImp, do đó, trong một thiết kế tốt, và thử nghiệm đơn vị tinh khiết của LoginPresenterImp, giống như của bạn, nó hoàn toàn bình thường mà nó không bao giờ được gọi. Giải pháp Tôi đề nghị là để kiểm tra LoginModelImp riêng:

public class LoginModelImpTest { 

    private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; 
    private LoginModelImp loginModelImp; 

    @Before 
    public void setUp() throws Exception { 
     mMockOnLoginCompletedListener = Mockito.mock(LoginModelContract.OnLoginCompletedListener.class); 
     loginModelImp = new LoginModelImp(); 
    } 

    @Test 
    public void shouldSuccessWithValidCredentials() { 

     loginModelImp.login("steve", "1234", mMockOnLoginCompletedListener);; 

     verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); 

     verify(mMockOnLoginCompletedListener, never()).onPasswordError(); 
     verify(mMockOnLoginCompletedListener, never()).onUsernameError(); 
    } 
} 

Ngoài ra, bạn phải sử dụng việc thực hiện thực tế của LoginModelImp trong bạn gián điệp LoginPresenterImpTest và người nghe của bạn (có nghĩa là người dẫn chương trình chính nó) hoặc cấu hình mocks để làm cho họ gọi người nghe.Dưới đây là một ví dụ, nhưng tôi sẽ không sử dụng này một:

public class LoginPresenterImpTest { 
    private LoginFragmentViewContract mMockViewContract; 
    private LoginModelContract mModelContract; 
    private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; 
    private LoginPresenterContract<LoginFragmentViewContract> mLoginPresenterContract; 

    @Before 
    public void setUp() throws Exception { 
     mMockViewContract = Mockito.mock(LoginFragmentViewContract.class); 
     mModelContract = new LoginModelImp(); 
     LoginPresenterImp spyPresenterImp = Mockito.spy(new LoginPresenterImp(mModelContract)); 
     mLoginPresenterContract = spyPresenterImp; 
     mMockOnLoginCompletedListener = spyPresenterImp; 
     mLoginPresenterContract.attachView(mMockViewContract); 
    } 

    @Test 
    public void shouldSuccessWithValidCredentials() { 
     when(mMockViewContract.getUsername()).thenReturn("steve"); 
     when(mMockViewContract.getPassword()).thenReturn("1234"); 

     mLoginPresenterContract.validateCredentials(); 

     verify(mMockViewContract, times(1)).getUsername(); 
     verify(mMockViewContract, times(1)).getPassword(); 

     verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); 

     verify(mMockOnLoginCompletedListener, never()).onPasswordError(); 
     verify(mMockOnLoginCompletedListener, never()).onUsernameError(); 
    } 
} 
+0

cảm ơn, Điều này có vẻ như một giải pháp tốt. Chỉ cần chờ đợi để xem phản ứng khác. – ant2009

+0

Hey @Lorenzo Tôi đang đối mặt với cùng một vấn đề nhưng trong trường hợp của tôi, tôi muốn xác thực dựa trên cơ sở dữ liệu sqlite bất kỳ ý tưởng làm thế nào để làm điều đó? – Tony

0

Tôi có thể bị thiếu điểm, nhưng bạn đã thử sử dụng PowerMock chưa?

Bạn sẽ cần phụ thuộc sau:

  • testCompile "org.powermock: powermock-module-Junit4: 1.6.5"
  • testCompile "org.powermock: powermock-module-Junit4-quy tắc : 1.6.5"
  • testCompile "org.powermock: powermock-api-Mockito: 1.6.5"
  • testCompile "org.powermock: powermock-classloading-xStream: 1.6.5"

Và sau đó sử dụng nó theo cách này:

@PowerMockIgnore({ "org.mockito.*", "android.*" }) 
@PrepareForTest(DownloadPresenterContract.Events.class) 
public class DownloadModelTest { 

    @Rule 
    public PowerMockRule rule = new PowerMockRule(); 

    private DownloadPresenterContract.Events mockEvents; 

    @Before 
    public void setUp() throws Exception { 
     this.mockEvents = PowerMockito.spy(new DownloadPresenterContract.Events()); 

     PowerMockito.whenNew(DownloadPresenterContract.Events.class) 
        .withNoArguments() 
        .thenReturn(this.mockEvents); 
    } 

    @Test 
    public void testStaticMocking() { 

     //Do your logic, which should trigger mockEvents actions 

     Mockito.verify(this.mockEvents, Mockito.times(1)).onDownloadSuccess(); 
     //Or use this: 
     //PowerMockito.verifyPrivate(this.mockEvents, times(1)).invoke("onDownloadSuccess", "someParam"); 
} 

}

+0

Tôi gặp sự cố với PowerMockito.spy (mới DownloadPresenterContract.Events()) Vì bạn không thể tạo phiên bản giao diện mới. Tuy nhiên, tôi đã thay đổi mã trong câu hỏi của mình thành một thứ dễ hiểu hơn. Cảm ơn. – ant2009

+0

Bạn không cần phải tạo một thể hiện bằng PowerMockito.spy (mới DownloadPresenterContract.Events()). Bạn có thể làm theo cách này: PowerMockito.whenNew (DownloadPresenterContract.Events.class) .withNoArguments(). ThenReturn (Mockito.mock (DownloadPresenterContract.Events.class)). Nói cách khác, bạn nói với nó "không có vấn đề ai sẽ tạo ra một trường hợp mới này, cho anh ta một phản ứng giả". –

+0

Trong mọi trường hợp có hai lựa chọn: 1) bạn đang áp dụng ma thuật lớp học (như PowerMock) làm cho các proxy AST xung quanh các lớp; 2) bạn đang áp dụng ma thuật OOP chế giễu tất cả mọi người cho đến khi bạn có được những gì bạn cần. Tôi thích biến thể đầu tiên vì nó ngắn hơn. Ngay cả khi bạn đang giao dịch với một giao diện, cần có ai đó cụ thể đang gọi một tham chiếu constructor. –

1

Tôi nghĩ rằng bởi vì bạn đang chế giễu LoginModelContractOnLoginCompletedListener bạn không thể khẳng định rằng onUsernameError, onPasswordErroronSuccess đang thực sự được gọi là bởi vì chế giễu LoginModelContract các " phương thức đăng nhập thực sự (mà nên gọi các phương thức này) sẽ không được thực hiện nhưng chỉ có phương thức được giả định sẽ được gọi. Bạn có thể kích hoạt các phương pháp này với một cái gì đó như:

Mockito.doAnswer(new Answer<Void>() { 
    @Override 
    public Void answer(InvocationOnMock invocation) throws Throwable { 
     Object[] args = invocation.getArguments(); 
     OnLoginCompletedListener listener = (OnLoginCompletedListener) args[2]; 
     listener.onUsernameError(); 
     return null; 
    } 
}).when(mMockModelContract).login(anyString(), anyString(), any(OnLoginCompletedListener.class)).thenAnswer(); 

Nhưng nguyên nhân một thử nghiệm như vậy sẽ làm cho không cảm nhận được vì bạn đang gọi một cách rõ ràng những gì bạn đang cố gắng để kiểm tra.

Theo ý kiến ​​của tôi, bạn nên kiểm tra LoginModelContract mà không cần LoginFragmentViewContractLoginPresenterContract. Một cái gì đó như:

public class LoginPresenterImpTest { 
    private LoginModelContract mMockModelContract; 
    private LoginModelContract.OnLoginCompletedListener mMockOnLoginCompletedListener; 

    @Before 
    public void setUp() throws Exception { 
     mMockOnLoginCompletedListener = Mockito.mock(LoginModelContract.OnLoginCompletedListener.class); 
     mMockModelContract = new LoginModelContract(); 
    } 

    @Test 
    public void shouldSuccessWithValidCredentials() { 
     mMockModelContract.login("steve", "1234", mMockOnLoginCompletedListener); 

     verify(mMockOnLoginCompletedListener, times(1)).onSuccess(); 

     verify(mMockOnLoginCompletedListener, never()).onPasswordError(); 
     verify(mMockOnLoginCompletedListener, never()).onUsernameError(); 
    } 
} 
2

Điều này làm giảm sự khác biệt giữa Câu chuyện của người dùng và Trường hợp sử dụng. Trong trường hợp này, bạn có 1 câu chuyện người dùng (ví dụ "Là người dùng, tôi muốn đăng nhập, vì vậy tôi cung cấp tên người dùng và mật khẩu"), nhưng thực tế có ít nhất 3 trường hợp sử dụng: đúng tên người dùng/mật khẩu bên phải, tên người dùng/sai mật khẩu, sai Tên đăng nhập/phải mật khẩu, vv là một thực hành tốt nhất Nói chung, bạn muốn cho thử nghiệm tương ứng 1: 1 với trường hợp sử dụng, vì vậy tôi muốn giới thiệu một cái gì đó như thế này:

@Test 
public void shouldCompleteWithValidCredentials() { 
    mMockModelContract.login("steve", "1234", 
           mMockOnLoginCompletedListener); 

    verify(mMockOnLoginCompletedListener, times(1)).onSuccess();  
} 

@Test 
public void shouldNotCompleteWithInvalidUser() { 
    mMockModelContract.login("wrong_user", "1234", 
           mMockOnLoginCompletedListener); 
    verify(mMockOnLoginCompletedListener, 
          times(1)).onUsernameError();  
} 

@Test 
public void shouldNotCompleteWithInvalidPassword() { 
    mMockModelContract.login("steve", "wrong_password", 
         mMockOnLoginCompletedListener); 
    verify(mMockOnLoginCompletedListener, times(1)).onPasswordError(); 
} 

Nói cách khác, đối với Kiểm tra 1, bạn đang cố gắng xác minh tích cực rằng, khi tên người dùng và mật khẩu hoàn tất, thành công được gọi. Đối với Kiểm tra 2, bạn đang xác minh các điều kiện để gọi onUsernameError và cho 3, các điều kiện cho onPasswordError. Tất cả ba là những điều hợp lệ để kiểm tra, và bạn có quyền để xác minh họ được gọi, nhưng bạn cần phải đối xử với họ như là các trường hợp sử dụng khác nhau.

Để hoàn tất, tôi sẽ xác minh điều gì xảy ra trên Wrong_User/Wrong_Password và cũng xác minh điều gì sẽ xảy ra nếu có điều kiện Wrong_Password N lần (bạn có cần chặn Tài khoản không?).

Hy vọng điều này sẽ hữu ích. Chúc may mắn.

Các vấn đề liên quan