學習了基本知識後,就可以實戰了。Mockito的實際使用還是比較麻煩的。
因為在實際使用中,最常遇到的就是需要類比第三方類庫的行為。
比如現在有一個類FTPFileTransfer,實現了向FTP傳輸檔案的功能。這個類中使用了apache的ftp類
org.apache.commons.net.ftp.FTPClient;
現在測試FTPFileTransfer 這個類中的isFTPConnected方法, 希望類比無法串連ftp的情況,測試是否記錄了錯誤log:
public class FTPFileTransfer{
//為了測試當ftp連結不上時,是否真的會記log,我們必須mock一個假的FTPClient對象,用該對象傳遞/覆蓋掉真實的FTPClient對象ftp,並強制讓這個假對象返回"無法串連",然後看是否會記log.
private FTPClient ftp;
private boolean isFTPConnected(){
if (!ftp.isConnected()) {
LOGGER.error("Disconnected from FTP.");
}
}
因此使用mock需要解決的問題是: 如何用mock的FTP對象覆蓋掉真實代碼中調用的FTPClient。因此需要將mock對象傳遞進去。
這裡對原始碼有一個限制:
原始碼中必須使用set和get方法來設定/獲得ftp對象,這樣測試代碼可以使用set來傳遞mock對象。
或者測試代碼中寫一個方法覆蓋掉原始碼中執行個體化ftp對象的方法,且測試代碼中使用mock ftp對象。
下面的樣本中都假定原始碼中使用了get/set。
- 建立一個返回FTPFileTransfer instance的方法。
//使用mock()方法建立一個FTP的mock對象mockedFTP。
FTPClient mockedFTP = mock(FTPClient.class);
//Stub “無法串連”
when(mockedFTP.isConnected())).thenReturn("無法串連");
//寫一個get方法返回FTPFileTransfer的執行個體用來測試,將mockedFTP作為參數傳遞進去。同時在這個方法內部用set方法將FTPFileTransfer類中的成員變數FTPClient ftp更改為mockFTP。
//這樣我們就得到了一個FTPFileTransfer的執行個體,同時裡面的ftp已經變成了我們希望的mockFTP。
private FTPFileTransfer getMockTaskFileTransfer(final FTPClientmockedFTP) {
FTPFileTransfer test = new FTPFileTransfer("127.0.0.1", 8888, "//usr", "username", "password");
test.setFTPClient(mockedFTP);
return test;
}
@Test
public void testTransfer() throws SocketException, IOException{
FTPFileTransfer test = getMockTaskFileTransfer();
//得到這個執行個體以後,就直接調用這個執行個體的isFTPConnected方法,然後去log檔案裡找有沒有我們希望的log就行了。注意此時,mockedFTP一定會返回"無法串連",所以isFTPConnected一定會記log。
test.isFTPConnected();
}
測試完畢~~~
從名字就可以看出,通過建立被測試類別的子類,覆蓋掉被測試類別的getFTPClient()方法,將mock對象傳遞進去。
class MockFTPFileTransfer extends FTPFileTransfer{
public MockFTPFileTransfer(){
super("127.0.0.1", 8888, "//usr", "username", "password");
}
//原始碼中必須使用get來獲得ftp對象,否則mock不會生效
@Override
public FTPClient getFTPClient(){
FTPClient mockedFTP = mock(FTPClient.class);
when(mockedFTP.isConnected()).thenReturn(true);
return mockedFTP;
}
}
@Test
public void testTransfer() throws SocketException, IOException{
FTPFileTransfer test = new MockFTPFileTransfer();
test.isFTPConnected();
}
partial mock是1.8之後的新功能。通常情況下會使用mock出來的對象完全覆蓋掉被類比的對象,對於那些沒有stub的方法,則會返回build-in 類型的預設值。
@Test
public void testTransfer() throws SocketException, IOException{
//類比一個FTPClient對象,同時stub行為。
FTPClient mockedFTP = mock(FTPClient.class);
when(mockedFTP.isConnected()).thenReturn(true);
//spy可以類比一個real object
//這裡的可以認為是spy在real object上包了一層,除了getFTPClient()被覆蓋掉以外,其他方法仍然是真實對象的。
//這就是partial mock的概念: 僅僅用mock對象覆蓋來源物件的一部分,而不是全部。
FTPFileTransfer spyFTP= spy(new FTPFileTransfer("127.0.0.1", "//usr", "username", "password"));
when(spyFTP.getFTPClient()).thenReturn(mockedFTP);
spyFTP.setFTPClient(mockedFTP);
spyFTP.isFTPConnected();
}
完畢!
大家都明白了嗎?如果沒有明白可以回帖,我會回答的。
------------------------------------------------------------------------------------------------------------------------------------
應要求發一個完整的UT
import static org.junit.Assert.assertTrue;
import java.io.File;
import java.io.IOException;
import java.io.StringReader;
import javax.xml.soap.MessageFactory;
import javax.xml.soap.SOAPConnection;
import javax.xml.soap.SOAPException;
import javax.xml.soap.SOAPMessage;
import javax.xml.soap.SOAPPart;
import javax.xml.transform.stream.StreamSource;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentMatcher;
import static org.junit.Assert.*;
import org.apache.commons.io.FileUtils;
import static org.mockito.Matchers.argThat;
import static org.mockito.Mockito.*;
public class ASAPSoapMessageTest {
ASAPSoapMessage testASAPSoapMessage= new ASAPSoapMessage();
String testSOAPRequest = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema\"><LoginRequest xmlns=\"http://asap.schemas.tfn.thomsonreuters.com/Messages/Base/2010-03-01/\"/></s:Body></s:Envelope>";
File testFile = new File("testFile.xml");
String testSOAPAction = "http://fackSOAPAction/";
String testWebService = "http://fackWebService/";
String testReply = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"><s:Body><LoginResponse xmlns=\"http://asap.schemas.tfn.thomsonreuters.com/Messages/Base/2010-03-01/\"/></s:Body></s:Envelope>";
MessageFactory mf;
SOAPMessage SOAPRequestTest;
SOAPMessage replySOAPRequestTest;
@Before
public void testUp() throws SOAPException{
mf = MessageFactory.newInstance();
SOAPRequestTest = mf.createMessage();
replySOAPRequestTest = mf.createMessage();
}
@Test
public void testGetSoapContent() throws IOException, SOAPException{
String soapContentString = "";
StreamSource prepMsg = new StreamSource(new StringReader(testSOAPRequest));
SOAPPart sp = SOAPRequestTest.getSOAPPart();
sp.setContent(prepMsg);
soapContentString = testASAPSoapMessage.getSoapContent(SOAPRequestTest);
assertTrue(soapContentString.equalsIgnoreCase(testSOAPRequest));
}
@Test
public void testPrepareSOAPMessage() throws IOException{
FileUtils.writeStringToFile(this.testFile,this.testSOAPRequest);
assertTrue(testASAPSoapMessage.prepareSOAPMessage("testFile.xml").equalsIgnoreCase(testSOAPRequest));
}
@Test
public void testSendSOAPMessage() throws SOAPException, IOException{
SOAPPart sp = replySOAPRequestTest.getSOAPPart();
StreamSource prepMsg = new StreamSource(new StringReader(testReply));
sp.setContent(prepMsg);
SOAPConnection mockedSOAPConnection = mock(SOAPConnection.class);
when(mockedSOAPConnection.call(argThat(new IsSOAPMessage()), anyObject())).thenReturn(replySOAPRequestTest);
ASAPSoapMessage spyASAPSoapMessage = spy(new ASAPSoapMessage());
when(spyASAPSoapMessage.getSoapConnection()).thenReturn(mockedSOAPConnection);
SOAPMessage replySOAPMessage= spyASAPSoapMessage.sendSOAPMessage(testSOAPRequest, testWebService, testSOAPAction);
assertTrue(spyASAPSoapMessage.getSoapContent(replySOAPMessage).equalsIgnoreCase(testReply));
}
class IsSOAPMessage extends ArgumentMatcher<SOAPMessage> {
public boolean matches(Object soapMessage) {
return soapMessage instanceof SOAPMessage;
}
}
}