Tags:

Hướng dẫn sử dụng @Mock, @Spy, @Captor và @InjectMocks trong Mockito

Trong bài viết này chúng ta sẽ cùng nhau tìm hiểu một số annotation cơ bản và thường xuyên được sử dụng khi làm việc với Mockito là @Mock@Spy@Captor, and @InjectMocks.

Enable Mockito Annotations

Trước khi tìm hiểu cách sử dụng của các annotation trên chúng ta cần enable mockito để các chúng có thể hoạt động. Bài viết này sử dụng phiên bản Junit 5, để enable mockito chúng ta chỉ cần chú thích test-class với @ExtendWith.

@ExtendWith(MockitoExtension.class)
public class MockitoAnnotationTest {
    ...
}

@Mock

@Mock có lẽ là annotation được sử dụng nhiều nhất trong mockito dùng để khởi tạo các mock object tự động thay vì thực hiện thủ công bằng cách sử dụng Mockito.mock().

Giả sử chúng ta cần mock một ArrayList làm bằng cách thủ công sẽ như sau

@ExtendWith(MockitoExtension.class)
public class MockAnnotationExample {

    @Test
    public void whenNotUseMockAnnotation_thenCorrect() {
        List mockList = Mockito.mock(ArrayList.class);

        mockList.add("one");
        Mockito.verify(mockList).add("one");
        assertEquals(0, mockList.size());

        Mockito.when(mockList.size()).thenReturn(100);
        assertEquals(100, mockList.size());
    }
}

Bây giờ chúng ta sẽ sử dụng @Mock annotation để triển khai lại tương tự ví dụ trên.

@ExtendWith(MockitoExtension.class)
public class MockAnnotationExample {

    @Mock
    private List mockedList;

    @Test
    public void whenUseMockAnnotation_thenMockIsInjected() {
        mockedList.add("one");
        Mockito.verify(mockedList).add("one");
        assertEquals(0, mockedList.size());

        Mockito.when(mockedList.size()).thenReturn(100);
        assertEquals(100, mockedList.size());
    }
}

Việc sử dụng @Mock annotation sẽ giúp việc khởi tạo các mock object đơn giản và ngắn gọn hơn. Do vậy thông thường chúng ta được khuyến khích sử dụng annotation hơn.

@Spy

Tương tự với @Spy chúng ta sẽ làm một ví dụ bằng cách thủ công là dùng Mockito.spy().

@ExtendWith(MockitoExtension.class)
public class SpyAnnotationExample {

    @Test
    public void whenNotUseSpyAnnotation_thenCorrect() {
        List<String> spyList = Mockito.spy(new ArrayList<String>());

        spyList.add("one");
        spyList.add("two");

        Mockito.verify(spyList).add("one");
        Mockito.verify(spyList).add("two");

        assertEquals(2, spyList.size());

        Mockito.doReturn(100).when(spyList).size();
        assertEquals(100, spyList.size());
    }
}

Sử dụng @Spy sẽ giúp ví dụ trên trong gọn gàng hơn

@ExtendWith(MockitoExtension.class)
public class SpyAnnotationExample {

    @Spy
    List<String> spiedList = new ArrayList<>();

    @Test
    public void whenUseSpyAnnotation_thenSpyIsInjectedCorrectly() {
        spiedList.add("one");
        spiedList.add("two");

        Mockito.verify(spiedList).add("one");
        Mockito.verify(spiedList).add("two");

        assertEquals(2, spiedList.size());

        Mockito.doReturn(100).when(spiedList).size();
        assertEquals(100, spiedList.size());
    }
}

 @Captor

Captor là một tính năng hữu ích trong mockito cho phép lưu trữ giá trị các tham số được gọi thông qua các mock objects. Trước đây chúng ta có thể triển khai thủ công như sau

@ExtendWith(MockitoExtension.class)
public class CaptorAnnotationExample {
    
    @Test
    public void whenNotUseCaptorAnnotation_thenCorrect() {
        List mockList = Mockito.mock(List.class);
        ArgumentCaptor<String> arg = ArgumentCaptor.forClass(String.class);

        mockList.add("one");
        Mockito.verify(mockList).add(arg.capture());

        assertEquals("one", arg.getValue());
    }
}

Tương tự với việc sử dụng @Captor annotation

@ExtendWith(MockitoExtension.class)
public class CaptorAnnotationExample {

    @Mock
    List mockedList;

    @Captor
    ArgumentCaptor argCaptor;

    @Test
    public void whenUseCaptorAnnotation_thenTheSam() {
        mockedList.add("one");
        Mockito.verify(mockedList).add(argCaptor.capture());

        assertEquals("one", argCaptor.getValue());
    }
}

@InjectMocks

@InjectMocks annotation cũng là một annotation được sử dụng thường xuyên trong mockito dùng để tiêm các mock object vào các object cần test.

Ví dụ chúng ta có MyDictionary như sau

public class MyDictionary {
    Map<String, String> wordMap;

    public MyDictionary() {
        wordMap = new HashMap<String, String>();
    }
    public void add(final String word, final String meaning) {
        wordMap.put(word, meaning);
    }
    public String getMeaning(final String word) {
        return wordMap.get(word);
    }
}

Bây giờ chúng ta muốn triển khai một test-class với mục đích kiểm thử MyDictionary. Do vậy chúng ta sẽ chú thích MyDictionary với @InjectMocks ngầm hiểu rằng MyDictionary là một object thật còn những mock object khác nếu có và được sử dụng trong MyDictionary thì sẽ tự động tiêm (inject) vào MyDictionary object.

@ExtendWith(MockitoExtension.class)
public class InjectMockAnnotationExample {
    @Mock
    Map<String, String> wordMap;

    @InjectMocks
    MyDictionary dic = new MyDictionary();

    @Test
    public void whenUseInjectMocksAnnotation_thenCorrect() {
        Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");

        assertEquals("aMeaning", dic.getMeaning("aWord"));
    }
}

 Inject mock object vào Spy object

Tương tự với ví dụ trên, nhưng nếu chúng ta muốn inject mock object vào một Spy object như thế này

@ExtendWith(MockitoExtension.class)
public class InjectMockToSpy {
    
    @Mock
    Map<String, String> wordMap;

    @Spy
    MyDictionary spyDic = new MyDictionary();

    @Test
    public void whenUseInjectMocksAnnotation_thenCorrect() {
        Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");

        assertEquals("aMeaning", spyDic.getMeaning("aWord"));
    }
}

Thì rất tiếc, unit-test sẽ thực thi và trả về kết quả FALSE vì hiện tại mockito chưa hỗ trợ inject các mock object vào Spy. Để làm được điều này chúng ta phải thực hiện thử công bằng cách inject thông qua constructor hoặc setter method.

Trong ví dụ này chúng ta sẽ sử dụng setter method nhưng trước tiên cần chỉnh lại MyDictionary class một chút, thêm setWordMap() vào.

public class MyDictionary {
    Map<String, String> wordMap;
    // .. more code
    public void setWordMap(Map<String, String> wordMap) {
        this.wordMap = wordMap;
    }
}

Sau đó sử dụng method này để tiêm thủ công WordMap mock object vào MyDictionary object.

@ExtendWith(MockitoExtension.class)
public class InjectMockToSpy {

    @Mock
    private Map<String, String> wordMap;

    @Spy
    private MyDictionary spyDic;

    @BeforeEach
    public void beforeAeach() {
        spyDic.setWordMap(wordMap);
    }

    @Test
    public void whenUseInjectMocksAnnotation_thenCorrect() {
        Mockito.when(wordMap.get("aWord")).thenReturn("aMeaning");

        assertEquals("aMeaning", spyDic.getMeaning("aWord"));
    }
}

Tóm lược

Như vậy qua bài viết này chúng ta đã tìm hiểu qua các annotation cơ bản và thường được sử dụng nhất trong mockito. 

Các bạn có thể tham khảo mã nguồn đầy đủ tại: Mockito-core-anntations

Nguồn tham khảo

https://www.baeldung.com/mockito-annotations

0 0 votes
Article Rating
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x
()
x