雞尾酒
Mockito是Java的單元測試Mock框架。它的logo是一杯古巴最著名的雞尾酒Mojito,Mojito雞尾酒,源自古巴的哈瓦那,帶有濃厚的加勒比海風情。並不濃烈,但是喝一杯下去,臉上會泛起紅暈,象少女的羞澀。味道很清新,有一點青澀、有點甜蜜。

Stub & Mock
Stub和Mock是Test Double類型中的2種。Test Double一共有5種類型,Dummy,Stub,Spy,Mock,Fake。

Test Double是測試復制品,用來統稱模擬真實對象的假對象。因使用場景有略微不同,而有這5種類型。
- Dummy,通常只用來填充參數列表。有可能是null對象引用,或Object類實例等。
- Fake,是簡化版的實現,比如基於內存實現的數據庫,不會真的去做數據庫操作,用簡單的HashMap來存放數據。
- Stub,Stub用來替代SUT(System Under Test)依賴的組件,但是只模擬一個外部依賴,不做斷言。
- Spy,介於Stub和Mock之間。如果真實對象沒有被打樁,當調用Spy對象時,真實對象也會被調用。
- Mock,可以理解為Stub+Verification,既模擬外部依賴,也會定義預期結果。
看一個實例,發送郵件,
public interface MailService {
public void send(Message msg);
}
先寫個Stub,
public class MailServiceStub implements MailService {
private List<Message> messages = new ArrayList<Message>();
public void send(Message msg) {
messages.add(msg);
}
public int numberSent() {
return messages.size();
}
}
實現Stub的狀態驗證,
class OrderStateTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
MailServiceStub mailer = new MailServiceStub();
order.setMailer(mailer);
order.fill(warehouse);
assertEquals(1, mailer.numberSent());
}
只做了簡單的測試,斷言發出了1封郵件。沒有測試是否發送給了對的收件人,或者郵件正文是否正確。不過不影響跟Mock比較。
如果用Mock,會怎么寫呢?
class OrderInteractionTester...
public void testOrderSendsMailIfUnfilled() {
Order order = new Order(TALISKER, 51);
Mock warehouse = mock(Warehouse.class);
Mock mailer = mock(MailService.class);
order.setMailer((MailService) mailer.proxy());
mailer.expects(once()).method("send");
warehouse.expects(once()).method("hasInventory")
.withAnyArguments()
.will(returnValue(false));
order.fill((Warehouse) warehouse.proxy());
}
結合一張圖,就一下全明白了,

怎么喝Mockito?
添加Maven dependency,
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>3.3.3</version>
<scope>test</scope>
</dependency>
現在可以開始Mock了,先Mock一個List Interface試試,(示例只是玩語法,實際應使用instance)
//Let's import Mockito statically so that the code looks clearer
import static org.mockito.Mockito.*;
// mock creation
List mockedList = mock(List.class);
// using mock object
mockedList.add("one");
mockedList.clear();
// verification
verify(mockedList).add("one");
verify(mockedList).clear();
接着用Mockito來做Stub:
// You can mock concrete classes, not just interfaces
LinkedList mockedList = mock(LinkedList.class);
// stubbing
when(mockedList.get(0)).thenReturn("first");
when(mockedList.get(1)).thenThrow(new RuntimeException());
// following prints "first"
System.out.println(mockedList.get(0));
// following throws runtime exception
System.out.println(mockedList.get(1));
// following prints "null" because get(999) was not stubbed
System.out.println(mockedList.get(999));
// Although it is possible to verify a stubbed invocation, usually it's just redundant
// If your code cares what get(0) returns, then something else breaks
// (often even before verify() gets executed).
// If your code doesn't care what get(0) returns, then it should not be stubbed.
verify(mockedList).get(0);
mock默認會返回null,或原始值,或空集合。如int/Integer返回0,boolean/Boolean返回false。
小結
本文首先介紹了Mockito是Java的單元測試Mock框架,然后比較了單元測試中Stub和Mock的區別,最后演示了Mockito如何使用。
參考資料:
https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html
