post-image

Một số annotation của Mockito

Testing

Trong bài viết trước, mình đã giới thiệu về Mockito. Trong bài viết này chúng ta sẽ cùng tìm hiểu một số annotation hay được sử dụng của Mockito như @Mock@Spy,…

1. 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);
}Code language: CSS (css)

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
}Code language: PHP (php)

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");
    }
}Code language: JavaScript (javascript)

2. 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

1.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());
    }
}Code language: JavaScript (javascript)
2. 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();
}Code language: JavaScript (javascript)

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());
    }
}Code language: JavaScript (javascript)
3. 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());
    }
}Code language: JavaScript (javascript)

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());
    }
}Code language: JavaScript (javascript)

@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);
}Code language: PHP (php)

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";
    }
}Code language: PHP (php)

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("[email protected]")).thenReturn(false);
 
        // Use mock in test
        Assert.assertEquals("FAILED", userService.createUser("[email protected]"));
    }
 
    @Test
    public void createUser_WhenEmailNotExisted_ReturnSuccess() {
        // Define return value for method createUser()
        Mockito.when(userDao.createUser("[email protected]")).thenReturn(true);
 
        // Use mock in test
        Assert.assertEquals("SUCCESS", userService.createUser("[email protected]"));
    }
}Code language: JavaScript (javascript)

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"));
    }
}Code language: JavaScript (javascript)

Nguồn: https://gpcoder.com/5392-mockito-annotations/

Leave a Reply

Your email address will not be published. Required fields are marked *