Trong bài viết trước chúng ta đã cùng tìm hiểu về Mockito và cách viết JUnit với Mockito. Trong bài viết này chúng ta sẽ cùng tìm hiểu một số Annotation của Mockito như @Mock, @Spy, @Captor, @InjectMocks để viết test cho các behavior.
Làm thế nào để sử dụng các Annotation của Mockito?
Trước khi đi vào chi tiết từng Annotation của Mockito, chúng ta cùng xem một số cách để sử dụng Annotation của Mockito.
Sử dụng phương thức MockitoAnnotations.initMocks(this), phương thức này phải được gọi trong @Before method.
@Before public void initMocks() { MockitoAnnotations.initMocks(this); }
Một cách khác để bật Annotation của Mockito là đánh dấu @RunWith(MockitoJUnitRunner.class) ở mức class, cách này thường được sử dụng.
@RunWith(org.mockito.junit.MockitoJUnitRunner.class) public MockitoExampleTest { // Test methods }
Ngoài các cách trên, chúng ta cũng có thể sử dụng Rule: MockitoJUnit.rule().
import java.util.List; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; public class MockitoExampleTest { @Rule public MockitoRule rule = MockitoJUnit.rule(); @Mock private List<String> list; @Test public void shouldDoSomething() { list.add("gpcoder.com"); } }
Một số Annotation của Mockito
@Mock
Annotation @Mock được sử dụng để khởi tạo một mock object và inject giá trị này cho field này. Chúng ta không tạo ra các đối tượng thực sự, thay vào đó yêu cầu Mockito tạo ra một đối tượng giả cho class này, các phương thức của class này không được thực thi thực sự, do đó trạng thái của đối tượng không bị thay đổi.
Ngoài cách sử dụng @Mock trên mức field, chúng ta có thể sử dụng phương thức Mockito.mock(), nó cho cùng kết quả với @Mock. Cách sử dụng Annotation ngắn hơn, dễ hiểu hơn hơn và thường được sử dụng hơn.
Ví dụ sử dụng @Mock
Các bạn có thể xem thêm ở bài viết Giới thiệu Mockito.
Ví dụ sử dụng Mockito.mock(classToMock)
package com.gpcoder.mockito.mock; import java.util.List; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; public class MockMethodTest { @Test public void test() { // Mock creation List<String> mockedList = Mockito.mock(List.class); // Using mock object mockedList.add("gpcoder.com"); // Verifies certain behavior happened once Mockito.verify(mockedList).add("gpcoder.com"); // Method add() is not really called, // it run on mocked object, so the size always is 0 Assert.assertEquals(0, mockedList.size()); // We can assign the size of mocked object Mockito.when(mockedList.size()).thenReturn(5); Assert.assertEquals(5, mockedList.size()); } }
Ví dụ các default value của một Mock object
DefaultValue.java
package com.gpcoder.mockito.mock; import java.util.Collection; import java.util.Optional; import java.util.stream.Stream; public interface DefaultValue { int getInt(); Integer getInteger(); double getDouble(); boolean getBoolean(); String getObject(); Collection<String> getCollection(); String[] getArray(); Stream<?> getStream(); Optional<?> getOptional(); }
DefaultValueOfMockObjectTest.java
package com.gpcoder.mockito.mock; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import java.util.Collections; import org.junit.Test; import org.mockito.Mockito; public class DefaultValueOfMockObjectTest { @Test public void test() { DefaultValue demo = Mockito.mock(DefaultValue.class); assertEquals(0, demo.getInt()); assertEquals(0, demo.getInteger().intValue()); assertEquals(0d, demo.getDouble(), 0d); assertFalse(demo.getBoolean()); assertNull(demo.getObject()); assertEquals(Collections.emptyList(), demo.getCollection()); assertNull(demo.getArray()); assertEquals(0L, demo.getStream().count()); assertFalse(demo.getOptional().isPresent()); } }
Thay đổi default value của mock object
Mockito cho phép chúng ta định nghĩa lại các default value của mock object thông qua các phương thức:
- Mockito.mock(Class<T> classToMock, Answer defaultAnswer) : cho phép tạo một mock object với loại Answer được chỉ định. Loại Answer chỉ định cách xử lý mặc định nếu một stub method không được xác định.
- Mockito.mock(Class<T> classToMock, MockSettings mockSettings) : cho phép tạo một mock object vói các setting được chỉ định. Các setting này có thể là defaultAnswer(), serializable(), name(), …
Các Answer được hỗ trợ bởi Mockito:
- RETURNS_DEFAULTS : đây là default Answer nếu không được chỉ định. Nó trả về các giá trị “empty”, tức là null, 0, false, empty collection.
- RETURNS_SMART_NULLS : tránh return null có thể gây ra lỗi NullPointerException. Nó trả về một SmartNull object. Phương thức test với object này vẫn bị fail, nhưng chúng ta có thể stack trace một unstubbed method chưa được gọi.
- RETURNS_MOCKS : Mockito cố gắng return các empty value trước, sau đó đến mock object, cuối cùng sẽ return null. Chẳng hạn, đối với các String hoặc array, nếu nó không được stub thì Mockito sẽ trả về null. Nếu sử dụng loại Answer này, kết quả trả về là một emty string, empty array.
- RETURNS_DEEP_STUBS : cho phép deep stub. Ví dụ, when(mock.getBar().getName()).thenReturn(“deep”);
- CALLS_REAL_METHODS : gọi một real method nếu các method không được stub, tương tự như sử dụng @Spy.
Chi tiết về các loại Answer, MockSettings và ví dụ các bạn xem thêm trên document của Mockito.
@Spy
Annotation @Spy được sử dụng để wrap một object thật, có thể gọi xử lý thật sự ở object này, tuy nhiên chúng ta có thể spy một số phương thức trên đối tượng thật như với @Mock.
Ngoài cách sử dụng @Spy, chúng ta có thể sử dụng phương thức Mockito.spy(), nó cho cùng kết quả với @Spy.
Ví dụ sử dụng @Spy
package com.gpcoder.mockito.spy; import java.util.ArrayList; import java.util.List; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; import org.mockito.Spy; @RunWith(org.mockito.junit.MockitoJUnitRunner.class) public class SpyAnnotationTest { @Spy private List<String> spyList = new ArrayList<>(); @Test public void test() { // Using mock object spyList.add("gpcoder.com"); // Method add() is really called, // it run on real object, so the size is 1 Assert.assertEquals(1, spyList.size()); Assert.assertEquals("gpcoder.com", spyList.get(0)); // We can assign the size of spy object Mockito.when(spyList.size()).thenReturn(5); Assert.assertEquals(5, spyList.size()); } }
Ví dụ sử dụng Mockito.spy()
package com.gpcoder.mockito.spy; import java.util.ArrayList; import java.util.List; import org.junit.Assert; import org.junit.Test; import org.mockito.Mockito; public class SpyMethodTest { @Test public void test() { List<String> spyList = Mockito.spy(new ArrayList<>()); // Using mock object spyList.add("gpcoder.com"); // Method add() is really called, // it run on real object, so the size is 1 Assert.assertEquals(1, spyList.size()); Assert.assertEquals("gpcoder.com", spyList.get(0)); // We can assign the size of spy object Mockito.when(spyList.size()).thenReturn(5); Assert.assertEquals(5, spyList.size()); } }
@InjectMocks
Trong một số trường hợp, chúng ta cần tạo một object test mà object này chứa các dependency khác. Vì vậy, chúng ta cần phải tạo các Mock/ Spy object cho các dependency và inject chúng vào đối tượng test. Để làm được điều này, chúng ta có thể sử dụng Annotation @InjectMocks.
@InjectMocks được sử dụng ở mức field, để đánh dấu các field này cần inject các dependency. Mokito cố gắng inject các giá trị cho các field này thông qua constructor, setter hoặc property injection. Nó sẽ không throw bất kỳ lỗi nào nếu không tìm được injection phù hợp.
Khả năng của @InjectMocks:
- Tạo một real instance object để test.
- Có thể gọi thực thi thực sự các phương thức được test.
- Inject các dependency đã được khởi tạo bằng mock object (@Mock) vào object test.
Sự khác nhau giữa @Mock và @InjectMocks:
- @Mock được sử dụng để tạo một mock object.
- @InjectMocks được sử dụng để khởi tạo một real object và inject các dependency.
Ví dụ sử dụng @InjectMocks
UserDao.java
package com.gpcoder.mockito.injectmocks; public interface UserDao { boolean createUser(String email); }
UserService.java
package com.gpcoder.mockito.injectmocks; public class UserService { private UserDao userDao; public UserService(UserDao userDao) { this.userDao = userDao; } public String createUser(String email) { boolean result = userDao.createUser(email); if (result) { // Send an email verify ... // Show a success message to end user ... return "SUCCESS"; } // Send an error message to end user ... return "FAILED"; } }
UserServiceTest.java
package com.gpcoder.mockito.injectmocks; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; // @RunWith attaches a runner with the test class to initialize the mock data @RunWith(MockitoJUnitRunner.class) public class UserServiceTest { // Create a mock object @Mock private UserDao userDao; // Create a mock object and inject dependency (UserDao) @InjectMocks private UserService userService; @Test public void createUser_WhenEmailExisted_ReturnFailed() { // Define return value for method createUser() Mockito.when(userDao.createUser("existed@gpcoder.com")).thenReturn(false); // Use mock in test Assert.assertEquals("FAILED", userService.createUser("existed@gpcoder.com")); } @Test public void createUser_WhenEmailNotExisted_ReturnSuccess() { // Define return value for method createUser() Mockito.when(userDao.createUser("not_existed@gpcoder.com")).thenReturn(true); // Use mock in test Assert.assertEquals("SUCCESS", userService.createUser("not_existed@gpcoder.com")); } }
Chúng ta cùng xem lại cách sử dụng @InjectMocks trong ví dụ trên:
- Đầu tiên, chúng ta sử @Mock để tạo các mock object cho các dependency. Trong ví dụ này là UserDao.
- Định nghĩa các behavior cho các mock object thông qua phương thức when() -> thenRetrun(). Các behavior này được gọi khi phương thức của test object được thực thi.
- @InjectMocks : được sử dụng để tạo real instance object và inject các dependency trên cho instance này. Trong ví dụ này, một instance của UserService sẽ được tạo và được inject một UserDao mock object.
@Captor
Annotation @Captor được sử dụng để tạo một instance cho ArgumentCaptor. Lớp ArgumentCaptor cho phép truy cập các đối số của các phương thức được gọi trong quá trình verify. Vì vậy chúng ta có thể lấy được các đối số này và sử dụng chúng để test.
Ví dụ sử dụng @Captor
package com.gpcoder.mockito.captor; import static org.hamcrest.Matchers.hasItem; import java.util.Arrays; import java.util.List; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Captor; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; @RunWith(MockitoJUnitRunner.class) public class CaptorTest { @Captor private ArgumentCaptor<List<String>> captor; @Test public final void shouldHasListItem() { List<String> asList = Arrays.asList("gpcoder.com", "mockito", "junit"); final List<String> mockedList = Mockito.mock(List.class); mockedList.addAll(asList); Mockito.verify(mockedList).addAll(captor.capture()); Assert.assertEquals(0, mockedList.size()); // No changed because it is a mock object // Verify value on argument final List<String> capturedArgument = captor.getValue(); Assert.assertEquals(3, capturedArgument.size()); Assert.assertThat(capturedArgument, hasItem("gpcoder.com")); } }
Tài liệu tham khảo: