introduce
Mockito is an open source testing mock framework for Java that provides a very clean and concise API, named after the classic cocktail Mojito.
What is a mock? A mock is a simulation. With a definition of a class or interface, we can create a mock object that simulates its behavior without having to provide a real implementation of the class or interface. This way, when writing unit tests, we just mock out the other dependencies, assuming their expected return, and can focus on testing our own implementation logic.
Sounds good, doesn’t it? Try it.
First taste
The introduction of Mockito
Mockito supports Gradle, Maven, Jar packages, and spring-boot-starter-test integration by default if using Spring Boot.
The Mockito used below is version 3.6.0 and the project code is based on Spring Boot.
An introduction to the operating
Let’s start with two of the simplest chestnuts in the official document
Verify interaction:
import static org.mockito.Mockito.*; // mock create a List List mockedList = mock(list.class); // Call the mock object method mockedList.add("one"); mockedList.clear(); Verify (mockedList).add("one"); // Verify (mockedList).add("one"); verify(mockedList).clear();Copy the code
The mock call returns:
// mock create a LinkedList LinkedList mockedList = mock(linkedList.class); // Using stubbing, if mockedList.get(0) is called, it returns "first" when(mockedList.get(0)). ThenReturn ("first"); // The console will print "first" system.out.println (mockedList.get(0)); // The console prints "null" because we did not assume the return value of get(999) system.out.println (mockedList.get(999));Copy the code
How about that? Doesn’t it look simple, and the syntax is natural.
Mockito is essentially an application of proxy patterns. Mocks create proxy objects, set the return values using stubs before the proxy is called, and the proxy records and tracks the behavior.
Drinking is tie-in
doSomething()
Void methods or spy objects. Use doThrow(), doAnswer(), doNothing(), doReturn(), doCallRealMethod() for mock actions.
Make a distinction between a mock, which is a full proxy, and a spy, which is a partial mock, which can call real methods and be traced for validation
List list = new LinkedList(); List spy = spy(list); / / here will be thrown IndexOutOfBoundsException, because call the real method, and the list is actually a empty list when (spy. Get (0)). ThenReturn (" foo "); // Use doReturn() to set the spy.get(0) simulation to return doReturn("foo").when(spy).get(0);Copy the code
Use ArgumentCapture to catch and validate incoming arguments
Validating the behavior alone is not enough; if you also need to validate the interface call input argument, you can use ArgumentCapture
// Initialize ArgumentCaptor ArgumentCaptor<Order> orderArgumentCaptor = ArgumentCaptor.forclass (order.class); / / in the call to ArgumentCaptor. The capture () verify (orderDAO, timeout (1)), update (orderArgumentCaptor. The capture ()); / / orderArgumentCaptor captured just now, the order parameter of the incoming call by getValue () to obtain the specific parameter object order updatedOrder = orderArgumentCaptor. The getValue (); assertEquals(updatedOrder.getStatus(), "processing");Copy the code
Practical annotation
Mock objects can be easily mocked and injected using @mock, @spy, and @InjectMocks annotations
@InjectMocks attempts to inject objects identified by @mock, @spy annotations through constructors, setters, and fields
Public class MockitoTest {/** * PostService mocks the PostRepository; */ @mock PostRepository PostRepository; @InjectMocks PostService postService; }Copy the code
The @captor annotation makes it easy to create ArgumentCaptor
@Captor ArgumentCaptor<Order> orderArgumentCaptor; @test public void Test () {/** * @captor * ArgumentCaptor<Order> orderArgumentCaptor = ArgumentCaptor.forClass(Order.class); */ verify(orderDAO, timeout(1)).update(orderArgumentCaptor.capture()); }Copy the code
BDD
Mockito provides the syntax sugar of BDD and can write test cases directly according to the given When then syntax of BDD
import static org.mockito.BDDMockito.*;
Seller seller = mock(Seller.class);
Shop shop = new Shop(seller);
public void shouldBuyBread() throws Exception {
//given
given(seller.askForBread()).willReturn(new Bread());
//when
Goods goods = shop.buyBread();
//then
assertThat(goods, containBread());
}
Copy the code
Practice a
In a very simple business scenario, OrderService provides the ability to place an Order, query and update the Order status through OrderDAO, and then call OperateLogService to save the operation information.
/** */ @data public class Order {private Long id; private String status; } /** * public interface OrderDAO {Order findById(Long orderId); void update(Order order); } /** * public interface OperateLogService {void addRecord(Long orderId, String message); } /** * public class OrderService {private OperateLogService OperateLogService; private OrderDAO orderDAO; @Autowired public void setOperateLogService(OperateLogService operateLogService) { this.operateLogService = operateLogService; } @Autowired public void setOrderDAO(OrderDAO orderDAO) { this.orderDAO = orderDAO; } /** * Place order operation, update order status and save, * @param orderId orderId */ public void placeOrder(Long orderId) {Order Order = orderDao.findById (orderId); If (order == null) {throw new RuntimeException(" order does not exist "); } order.setStatus("processing"); orderDAO.update(order); OperateLogService. AddRecord (orderId, "create order"); }}Copy the code
In this example, we need to write unit tests for the orderService.placeOrder () method.
A separate test case is required to verify that an exception is thrown.
Orderdao.update () is the focus of validation. This method needs to be called normally and the order status changes to “processing”. OperateLogService is a third-party service. In order logic, we do not need to care about its specific implementation logic. We mock directly and only need to verify that it is called normally.
import static org.junit.Assert.*; import static org.mockito.Mockito.*; import static org.mockito.BDDMockito.*; public class OrderServiceTest { @Mock OrderDAO orderDAO; @Mock OperateLogService operateLogService; @InjectMocks OrderService orderService; @Captor ArgumentCaptor<Order> orderArgumentCaptor; @test public void place_order_should_throw_EXCEPtion_when_order_not_exist () {// given // mock query method returns null given(orderDAO.findById(anyLong())) .willReturn(null); // When // then // Validation throws an exception RuntimeException = assertThrows(RuntimeException. Class, () -> orderService.placeOrder(1L)); AssertEquals (exception.getMessage(), "Order does not exist "); } @test public void place_order_should_update_order_status_then_save() {// given // mock query returns an order long orderId = 1L; Order order = new Order(); order.setId(orderId); order.setStatus("init"); given(orderDAO.findById(orderId)) .willReturn(order); Orderservice.placeorder (1L); / / then / / verification orderDAO. The update is called a then (orderDAO.) should (times (1)), update (orderArgumentCaptor. The capture ()); / / validate the Order status is updated Order updatedOrder = orderArgumentCaptor. GetValue (); assertEquals(updatedOrder.getId(), orderId); assertEquals(updatedOrder.getStatus(), "processing"); Then (operateLogService).should().addRecord(eq(orderId), anyString()); }}Copy the code
The unit test uses the AFOREMENTIONED BDD, annotations, and parameter catchers, and the test code is clear and easy to write.
Add some more material
AssertJ is best served with AssertJ
AssertJ provides a smoother assertion API than JUnit’s assertion and is easy to extend
import static org.assertj.core.api.Assertions.*; assertEquals(expected, actual); -> assertThat(actual).isEqualTo(expected); assertThatThrownBy(() -> {methodWillThrow(); }).isinstanceof (someexception.class).hasMessage(" parameter cannot be empty ");Copy the code
PowerMockito
Mockito does not support mock tests for static methods, constructors, final methods, private methods, etc. PowerMock can be used to solve this problem.
PowerMock also provides PowerMockito to work with the Mockito API.
Mock final Methods/classes are supported by Mockito 2.1.0
orderService = PowerMockito.spy(orderService);
PowerMockito.doReturn(any()).when(orderService, "privateMethod", any());
PowerMockito.verifyPrivate(orderService).invoke("privateMethod", any());
Copy the code
TDD
TDD stands for Test-Driven Development.
Review a common development process, start with requirements analysis, then break down tasks, then implement concrete logic, and finally write a single test. Putting unit tests at the last step makes it easy to miss trivial changes, and some design problems are exposed late in the game. As requirements iterate, it’s easy for developers to get lazy and “forget” to write unit tests.
Looking back at the whole process, when the task is decomposed, it is actually the design process before the concrete logic is realized. This phase designs the required classes, methods, and interactions between them. These designs should be concrete and verifiable.
Therefore, TDD can be adopted, and the process of writing test cases is presupposed to the design stage. The decomposed task unit is actually one or more verifiable test cases. When writing test cases, you can “force” yourself to think about the rationality of the design, and then immediately implement and verify it.
Keep to the test -> Implement -> Refactor (red -> green -> yellow) rhythm while TDD is in progress. It is recommended to have a handy rerun Test shortcut on the IDE.
conclusion
Mockito’s easy-to-understand API, combined with some of the practices mentioned above, makes writing single tests time-consuming and effortless.
🍻 cheers ~
reference
Official Mockito documentation
AssertJ official document
PowerMock official documentation