Một số annotation của Mockito
NỘI DUNG BÀI VIẾT
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)
Leave a Reply