学习了基本知识后,就可以实战了。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。
//使用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;
}
}
}