2012-08-07 156 views
0

我正在为我的Java应用程序编写单元测试,我需要为可能引发的潜在JiBX异常编写一个测试。我正在测试的方法调用另一个类的方法,其中JiBX异常可能会被抛出。这是我测试的类(姑且称之为A类):可以使用Mockito来模拟org.jibx.runtime.BindingDirectory吗?

@Inject 
private CommonDAL commonDAL; 

@Async 
public Future<String> getTransactionalXXXAvailability(
     List<XXXAvailRequestEntry> requestEntries, TravelWindow travelWindow) { 
    if (requestEntries.size() == 0) 
     return null; 
    XXXAvailRqAccessor requestAccessor = new XXXAvailRequestBuilder().buildRequest(requestEntries, travelWindow); 
    logger.info(requestAccessor.marshalRequest()); 
    String responseAsXml = null; 
    try { 
     responseAsXml = getResponse(requestAccessor.getRequest()); 
    } catch (JiBXException e) { 
     logger.error("Problem unmarshaling the XXX avail response: ", e); 
    } 

    logger.info(responseAsXml); 
    return new AsyncResult<String>(responseAsXml); 
} 

private String getResponse(OTAXXXAvailRQ request) throws JiBXException { 
    HbsiConnectionInfo connectionInfo = new HbsiConnectionInfo(); 
    connectionInfo.useConnectionInfoFromContext(); 

    HBSIXML4OTAWebserviceSoap hbsiSoap = getHbsiSoapService(connectionInfo); 

    InterfacePayload header = new InterfacePayload(); 
    header.setChannelIdentifierId("XXXXXXXXX"); 
    header.setVersion("2005B"); 
    header.setInterface("HBSI XML 4 OTA"); 

    ComponentInfo componentInfo = new ComponentInfo(); 
    XXXAvailRqAccessor requestAccessor = new XXXAvailRqAccessor(request); 
    componentInfo.setId(requestAccessor.getFirstXXXCode()); 
    componentInfo.setUser(connectionInfo.getUsername()); 
    componentInfo.setPwd(connectionInfo.getPassword()); 
    componentInfo.setComponentType(EComponentType.XXX); 

    Login login = new Login(); 
    login.setComponentInfo(componentInfo); 

    Message body = new Message(); 
    // todo: this needs to be unique for every request. 
    // todo: hook up to logging 
    body.setRequestId(UUID.randomUUID().toString()); 
    body.setTransaction(ETransaction.XXX_AVAIL_RQ); 

    body.setXML(requestAccessor.marshalRequest()); 

    return hbsiSoap.getSoapRequest(header, body, login); 
} 

HBSIXML4OTAWebserviceSoap getHbsiSoapService(HbsiConnectionInfo connectionInfo) { 
    HBSIXML4OTAWebservice ws = new HBSIXML4OTAWebservice(connectionInfo.getWsdlLocation()); 

    HBSIXML4OTAWebserviceSoap hbsiSoap = ws.getHBSIXML4OTAWebserviceSoap(); 
    Map<String, Object> requestContext = ((BindingProvider)hbsiSoap).getRequestContext(); 
    String readTimeout = commonDAL.getPropertyValue(new PropertyKey(Section.HBSI, 
      Property.HBSI_WS_READ_TIMEOUT)); 
    requestContext.put(BindingProviderProperties.REQUEST_TIMEOUT, Integer.parseInt(readTimeout)); 
    String connectionTimeout = commonDAL.getPropertyValue(new PropertyKey(Section.HBSI, 
      Property.HBSI_WS_CONNECTION_TIMEOUT)); 
    requestContext.put(BindingProviderProperties.CONNECT_TIMEOUT, Integer.parseInt(connectionTimeout)); 
    return hbsiSoap; 
} 

抛出误差的方法如下(从另一个类,我们称之为B类):

public String marshalRequest() { 
    StringWriter requestAsXml = new StringWriter(); 

    try { 
     IBindingFactory bindingFactory = BindingDirectory.getFactory(PROTECTEDCLASSNAME.class); 
     IMarshallingContext marshalingContext = bindingFactory.createMarshallingContext(); 
     marshalingContext.setIndent(2); 
     marshalingContext.setOutput(requestAsXml); 
     marshalingContext.marshalDocument(request); 

    } catch (JiBXException e) { 
     logger.error("Problem marshaling PROTECTEDCLASSNAME.", e); 
    } 

    return requestAsXml.toString(); 
} 

当“body.setXML(requestAccessor.marshalRequest());”被调用时,另一个类(requestAccessor)被测试访问,它的方法.marshalRequest是JiBX异常应该被抛出的地方。我正在编写的测试的目的是让这个A类的单元测试覆盖率达到100 &,但是被测系统至少由两个类组成,因为我无法模拟名为requestAccessor的XXXAvailRqAccessor对象。出于以下原因,我无法进行任何测试来产生此错误。

  • 称为requestAccessor的XXXAvailRqAccessor对象被我测试,所以我不能用一个模拟抛出异常的方法中实例化。

  • 传递给.getResponse()的OTAXXXAvailRQ参数不能被模拟,因为它是由构建器为XXXAvailRqAccessor创建的。

  • 我试着在IBindingFactory上监听,但它没有工作。我在B类中创建了一个实例化IBindingFactory的方法,这样我就可以窥探它,但那不起作用。

  • 我也尝试过使用PowerMock在实例化时返回模拟XXXAvailRqAccessor,但是当我试图模拟JiBXExceptioin for .getRequest时,Mockito说“检查异常对于此方法无效”。如果我不能让Mockito抛出这个错误,我不知道是否可以操纵关联的对象来抛出它。

回答

2

好吧不是真的,或者我至少不知道这样的方式。你可以,如果你真的想这样做(我反对它)在类中创建一个像这样的方法:

IBindingFactory getBindingFactory() { 
    return BindingDirectory.getFactory(PROTECTEDCLASSNAME.class); 
} 

而替换该行:

IBindingFactory bindingFactory = BindingDirectory.getFactory(PROTECTEDCLASSNAME.class); 

有了:

IBindingFactory bindingFactory = getBindingFactory(); 

然后你可以探测()(如果你不熟悉它,你可以在文档中的Mockito.spy()上读到)这个对象,并使这个方法返回一个模拟。从这一点来看,这是一帆风顺的。

这种做法是不劝,但因为:

  1. 要创建一个新的方法(无用的)只是用于测试
  2. 该方法必须是用于测试可见,所以你不能将其设为私有...
  3. 我不是间谍一般

问题一个巨大的风扇仍然是:如何正确测试这样的情况。那么在大多数情况下,我尽可能地重构,有时候它会有所帮助。而在其他情况下......我还没有想出更好的解决方案。

+0

其实这是一个很好的解决方案。创建仅用于测试的新方法没有任何问题。最好有一个很好的可测试的类,而不是少一个方法。新方法必须是包私有的,这非常合适。这种类型的问题是间谍少数真正的好用途之一。还有另一种方法可以做到这一点,您可能更喜欢 - 我会在几个小时内发布答案(如果我记得的话)。 – 2012-08-08 02:23:48

1

正如我在评论中所说的,我完全主张Mateusz的解决方案。但有一个选择。在具有marshalRequest方法的类中,有一个类型为IBindingFactory的私有最终字段。也在这个类中,有一个包含私有构造函数的额外参数,即IBindingFactory来设置。正常的构造函数将调用BindingDirectory.getFactory(...)然后调用新的构造函数。因此,如果标准构造函数只有一个String参数,则该类可能看起来像这样。

public class MyClass{ 
    private String name; 
    private IBindingFactory bindingFactory; 

    public MyClass(String name){ 
     this(name, BindingDirectory.getFactory(PROTECTEDCLASSNAME.class)); 
    } 

    MyClass(String name, IBindingFactory bindingFactory){ 
     this.name = name; 
     this.bindingFactory = bindingFactory; 
    } 

    public String marshalRequest() { 
     StringWriter requestAsXml = new StringWriter(); 

     try { 
      IMarshallingContext marshalingContext = bindingFactory.createMarshallingContext(); 
      marshalingContext.setIndent(2); 
      marshalingContext.setOutput(requestAsXml); 
      marshalingContext.marshalDocument(request); 

     } catch (JiBXException e) { 
      logger.error("Problem marshaling PROTECTEDCLASSNAME.", e); 
     } 

     return requestAsXml.toString(); 
    } 
} 

这样做的原因是这样的话,在测试你的类,你可以创建一个模拟IBindingFactory,并把它传递到包私有构造。

+0

我正在写的测试是针对一个调用另一个类的'marshalRequest'的类。如果我在'MyClass'中创建一个私有构造函数并且'TestClass'从来没有调用它,那么我不能插入一个模拟IBindingFactory作为正确的参数?除非我可以在'TestClass'中创建一个mockBindingFactory并将其传递给'MyClass'构造函数。 – tamuren 2012-08-10 17:11:48

+0

我不太明白。你是说你的被测系统(SUT)由两个类组成?或者只是调用具有'marshalRequest'方法的类?如果你的SUT没有包含'marshalRequest'方法,那么你应该存根整个方法,而不是嘲笑'IBindingFactory'。也许你可以通过发布你实际尝试测试的类的代码来澄清你的需求;那么我可能会更好地帮助你。 – 2012-08-10 22:33:05