Giới thiệu
Khi viết Unit Test, chúng ta cần thực hiện một số Assert để xác nhận expected result và actual result là như nhau. Đối với các mock object, chúng ta cũng cần verify một vài behavior đã được gọi hay chưa.
Trong Mockito, chúng ta có thể thực hiện verify các mock object thông qua phương thức Mockito.verfify():
verify(mockedObject, times(2)).someMethodOfMockObject(argurments);
Một số ví dụ Verifying Behavior
Thông thường, viết Unit Test sử dụng Mockito bao gồm các bước cơ bản sau:
- Tạo mock object.
- Điều khiển một mock object và xác định phải làm gì khi các phương thức cụ thể của mock object được gọi.
- Gọi phương thức.
- Verify phương thức đã được gọi hay chưa.
Ví dụ:
// 1: Create mock object List mock = mock(List.class); // 2: Stub - Control mock’s behavior when(mock.add("gpcoder")).thenReturn(true); // 3: Call behavior mock.add("gpcoder"); // 4: Verify behavior verify(mock).add("gpcoder");
Verify số lần phương thức được gọi
Một số phương thức được sử dụng để verify số lần gọi:
- times(number) : verify chính xác số lần phương thức được gọi.
- never() : verify phương thức không bao giờ được gọi.
- atLeastOnce() : verify phương thức được gọi ít nhất 1 lần.
- atLeast(number) : verify phương thức được gọi ít nhất number lần.
- atMost(number) : verify phương thức được gọi nhiều nhất number lần.
- verifyNoMoreInteractions(mockedObjects) : xác nhận rằng không có bất kỳ một phương thức nào của mockedObject được gọi mà không được gọi verfify() để kiểm tra.
- verifyZeroInteractions() :tương tự như verifyNoMoreInteractions().
package com.gpcoder.mockito.verify; import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.Mockito; public class VerifyTest { private List<String> mockedList; @Before public void prepareForTest() { // Mock creation mockedList = Mockito.mock(List.class); } @Test public void zeroTimeTest() { // Verifies certain behavior never happened Mockito.verify(mockedList, Mockito.never()).add("gpcoder.com"); Mockito.verify(mockedList, Mockito.times(0)).add("gpcoder.com"); } @Test public void oneTimeTest() { // Using mock object mockedList.add("gpcoder.com"); // Verifies certain behavior happened once Mockito.verify(mockedList).add("gpcoder.com"); // Default one time Mockito.verify(mockedList, Mockito.times(1)).add("gpcoder.com"); } @Test public void atLeastTest() { // Using mock object mockedList.add("one"); mockedList.add("two"); mockedList.add("three"); // Verifies certain behavior happened at least once Mockito.verify(mockedList, Mockito.atLeastOnce()).add(Mockito.anyString()); // Verifies certain behavior happened at least twice Mockito.verify(mockedList, Mockito.atLeast(2)).add(Mockito.anyString()); } @Test public void atMostTest() { // Using mock object mockedList.add("one"); mockedList.add("two"); // Verifies certain behavior happened at most twice Mockito.verify(mockedList, Mockito.atMost(2)).add(Mockito.anyString()); } @Test public void verifyNoMoreInteractionsTest() { // Create mock object List<String> mockOne = Mockito.mock(List.class); // Using mocks - only mockOne is interacted mockOne.add("one"); // Uncomment this code will make the test case failed because it is unverified interaction // mockOne.get(0); // Ordinary verification Mockito.verify(mockOne).add("one"); // Checks if any of given mocks has any unverified interaction. You can use this // method after you verified your mocks - to make sure that nothingelse was // invoked on your mocks. Mockito.verifyNoMoreInteractions(mockOne); } @Test public void verifyZeroInteractionsTest() { // Create mock object List<String> mockOne = Mockito.mock(List.class); List<String> mockTwo = Mockito.mock(List.class); List<String> mockThree = Mockito.mock(List.class); // Using mocks - only mockOne is interacted mockOne.add("one"); // Ordinary verification Mockito.verify(mockOne).add("one"); // Verify that method was never called on a mock Mockito.verify(mockOne, Mockito.never()).add("two"); // Verifies that no interactions happened on given mocks beyond the previously // verified interactions Mockito.verifyZeroInteractions(mockTwo, mockThree); } @Test public void clearInvocationsTest() { // Create mock object List<String> mockOne = Mockito.mock(List.class); List<String> mockTwo = Mockito.mock(List.class); // Using mocks - only mockOne is interacted mockOne.add("one"); mockTwo.add("one"); // Use this method in order to only clear invocations, when stubbing is non-trivial. Mockito.clearInvocations(mockOne, mockTwo); // Another way: reset() a mock so that it can be reused later // Mockito.reset(mockOne, mockTwo); // Verifies that no interactions happened on given mocks beyond the previously // verified interactions Mockito.verifyZeroInteractions(mockOne, mockTwo); } }
Verify các tham số (argument) của phương thức
Đối với các void method, thông thường chúng ta có thể verify các thông tin:
- Số lần phương thức được gọi.
- Giá trị các tham số.
Để verify các tham số (argument) của các phương thức, chúng ta có thể sử dụng Argument Matcher hoặc Argument Captor.
package com.gpcoder.mockito.verify; import static org.hamcrest.Matchers.hasItem; import java.util.Arrays; import java.util.List; import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class ArgumentMatcherTest { @Captor private ArgumentCaptor<List<String>> captor; @Mock private List<String> mockedList; @Before public void prepareForTest() { MockitoAnnotations.initMocks(this); } @Test public final void argumentCaptorTest() { List<String> asList = Arrays.asList("gpcoder.com", "mockito", "junit"); mockedList.addAll(asList); // Verify value on arguments Mockito.verify(mockedList).addAll(captor.capture()); final List<String> capturedArgument = captor.getValue(); Assert.assertEquals(3, capturedArgument.size()); Assert.assertThat(capturedArgument, hasItem("gpcoder.com")); } @Test public void argumentMatcherTest() { List<String> asList = Arrays.asList("gpcoder.com", "mockito", "junit"); mockedList.addAll(asList); // Verifies certain behavior happened at at least once with the given value is // "gpcoder.com" Mockito.verify(mockedList).addAll(Mockito.anyCollection()); Mockito.verify(mockedList, Mockito.atLeast(1)) .addAll(Mockito.argThat(collection -> collection.contains("gpcoder.com"))); } }
Verify thứ tự phương thức được gọi
Trong một số trường hợp, chúng ta cần verify thứ tự thực thi các phương thức. Chẳng hạn, chức năng tạo user bao gồm các bước sau:
- Tạo mã user.
- Lưu thông tin user vào cơ sở dữ liệu.
- Gửi mail xác nhận.
- Hiển thị thông báo kết quả thêm thành công/ thất bại.
Trong Mockito, để verify thứ tự các phương thức được gọi chúng ta có thể sử dụng các phương thức hỗ trợ của class InOrder. Để tạo InOrder, chúng ta sử dụng phương thức Mockito.inOrder(mockedObjects).
UserCreationTemplate.java
package com.gpcoder.mockito.verify; public abstract class UserCreationTemplate { public void createUser() { createUserCode(); saveUser(); sendEmail(); showResult(); } protected abstract String createUserCode(); protected abstract void saveUser(); protected abstract void sendEmail(); protected abstract void showResult(); }
InOrderTest.java
package com.gpcoder.mockito.verify; import java.util.List; import org.junit.Test; import org.mockito.InOrder; import org.mockito.Mockito; public class InOrderTest { @Test public void singleMockTest() { // Create mock object List<String> mockedList = Mockito.mock(List.class); // Using mock object mockedList.add("was added first"); mockedList.add("was added second"); // Create an inOrder verifier for a single mock InOrder inOrder = Mockito.inOrder(mockedList); // Following will make sure that add is first called with "was added first", // then with "was added second" inOrder.verify(mockedList).add("was added first"); inOrder.verify(mockedList).add("was added second"); } @Test public void multipleMocksTest() { // Create mock object List<String> mockedList1 = Mockito.mock(List.class); List<String> mockedList2 = Mockito.mock(List.class); // Using mock object mockedList1.add("was added first for 1st list"); mockedList2.add("was added first for 2nd list"); mockedList1.add("was added second for 1st list"); // Create inOrder object passing any mocks that need to be verified in order InOrder inOrder = Mockito.inOrder(mockedList1, mockedList2); // Following will make sure that firstMock was called before secondMock inOrder.verify(mockedList1).add(Mockito.anyString()); inOrder.verify(mockedList2).add(Mockito.anyString()); inOrder.verify(mockedList1).add(Mockito.anyString()); } @Test public void verifyOrderingExecutionTest() { // Create mock object UserCreationTemplate mockedObject = Mockito.spy(UserCreationTemplate.class); // Using mock object mockedObject.createUser(); // Create inOrder object passing any mocks that need to be verified in order InOrder inOrder = Mockito.inOrder(mockedObject); // Verify ordering execution inOrder.verify(mockedObject).createUserCode(); inOrder.verify(mockedObject).saveUser(); inOrder.verify(mockedObject).sendEmail(); inOrder.verify(mockedObject).showResult(); } }
Verify thời gian thực thi (timeout)
Trong mockito, chúng ta cũng có thể verify thời gian thực thi của các phươn thức chạy async thông qua 2 phương thức:
- timeout(miliseconds) : verify phương thức được được gọi trong một khoảng thời gian miliseconds được chỉ định.
- after(miliseconds) : tương tự như timeout. Tuy nhiên, timeout() sẽ thoát ngay lập tức khi một phương thức đã được verify, còn after() sẽ chờ cho đến khi hết thời gian được chỉ định mới thực hiện verify.
CustomList.java
package com.gpcoder.mockito.verify; import java.util.List; import java.util.concurrent.TimeUnit; public class CustomList { public static final int TIME_TO_EXECUTE_IN_MILISECONDS = 50; private List<String> list; public List<String> getList() { return list; } public boolean add(String item) { Thread t = new Thread(new Runnable() { @Override public void run() { try { TimeUnit.MILLISECONDS.sleep(TIME_TO_EXECUTE_IN_MILISECONDS); } catch (InterruptedException e) { e.printStackTrace(); } getList().add(item); } }); t.start(); return true; } }
TimeoutTest.java
package com.gpcoder.mockito.verify; import java.util.List; import org.junit.Before; import org.junit.Test; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.MockitoAnnotations; public class TimeoutTest { @InjectMocks private CustomList mockedList; @Mock private List<String> list; @Before public void prepareForTest() { // Mock creation MockitoAnnotations.initMocks(this); } /** * Verification will be triggered after given amount of millis, allowing testing * of async code. * * timeout() exits immediately with success when verification passes */ @Test public void timeoutTest() { // Using mock object mockedList.add("gpcoder.com"); // Verify call to add method to be called once times within 100 ms Mockito.verify(list, Mockito.timeout(100)).add("gpcoder.com"); // Default once times Mockito.verify(list, Mockito.timeout(100).times(1)).add("gpcoder.com"); } /** * Verification will be triggered after given amount of millis, allowing testing * of async code. * * after() awaits full duration to check if verification passes */ @Test public void afterTest() { // Using mock object mockedList.add("gpcoder.com"); // Verify call to add method to be called once times within 100 ms Mockito.verify(list, Mockito.after(100)).add("gpcoder.com"); // Default once times Mockito.verify(list, Mockito.after(100).times(1)).add("gpcoder.com"); } }
Thử thay đổi giá trị TIME_TO_EXECUTE_IN_MILISECONDS là 50, 100, 150 và chạy thử để xem các kết quả khác nhau.
Tài liệu tham khảo: