- Yair Mark
Today I had to setup the test scaffolding which would be used to test a class we needed to refactor. The class we have to refactor takes data returned from various web service calls and uses that in its calculations. We have to refactor this class due to a serious bug identified in it and because the class itself is difficult to understand making it hard to diagnose the cause of this bug.
This existing class has some tests but they are written against dummy data which makes it difficult to test if the class is actually behaving correctly. There is a fairly large amount of upstream data needed for this class where the data is derived from SOAP services and many test cases with different versions of this data so coding up this data by hand is not an option. Because of this complexity I opted for creating a class that would be responsible for providing test data based on data captured during real web service calls.
Essentially what I had to do was:
- Take SOAP messages saved as xml files
- Convert these to Java objects
- Feed these objects to the class we need to refactor so we can test it properly
As we have test clients to communicate with these services I could leverage the objects that were generated there by JAXB. The question now is how do you easily convert XML files containing the full SOAP requests/responses to the object you want?
On a first attempt I simply tried what was suggested in this answer:
val unmarshaller = JAXBContext.newInstance(Person::class.java).createUnmarshaller() val person = unmarshaller.unmarshal(File("/path/to/personSoapRequestFile.xml")) as Person
This throws the following error:
javax.xml.bind.UnmarshalException: unexpected element (uri:"http://schemas.xmlsoap.org/soap/envelope/", local:"Envelope"). Expected elements are ...
This happens as the unmarshaller is expecting a message for the standalone
Person object. The XML in this file is a full blown SOAP message. Extracting just the person request payload by hand would be cumbersome and I want this test data provider to be able to handle test cases based on many extracted data sets.
I eventually found this answer and tweaked my code accordingly:
val message:SOAPMessage = MessageFactory.newInstance().createMessage(null, FileInputStreamReader( File("/path/to/personSoapRequestFile.xml"))) val unmarshaller = JAXBContext.newInstance(Person::class.java).createUnmarshaller() val person = unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument())) as Person
The key line in the above is
message.getSOAPBody().extractContentAsDocument() which is responsible for extracting the Person message out of the SOAP message.
The last hurdle I ran into was that this message was a different version of SOAP than what the marshaller was expecting:
javax.xml.soap.SOAPException: InputStream does not represent a valid SOAP 1.2 Message
I tried explicitly Googling this error but to no avail. Instead I took a different approach and searched for something like
MessageFactory SOAP 1.2 and bingo I came up with this blog post which indicated in the code sample there that there is a
SOAPConstants object that you can pass to the message factory when creating it. The complete solution I ended up with was:
val message:SOAPMessage = MessageFactory.newInstance(SOAPConstants.SOAP_1_2_PROTOCOL).createMessage(null, FileInputStreamReader( File("/path/to/personSoapRequestFile.xml"))) val unmarshaller = JAXBContext.newInstance(Person::class.java).createUnmarshaller() val person = unmarshaller.unmarshal(message.getSOAPBody().extractContentAsDocument())) as Person
Using this approach I was finally able to complete the test scaffolding. If you use a framework like JUnit 5 you can easily use its
@MethodSource annotation to hook a data provider like the one I described into your tests. In my next post I will likely get more into this as I start to flesh out the tests using the test data provider I created today.