a_wasink
15 years agoOccasional Contributor
Mock Response Query/Match not working
As I was setting up test cases in Soap-ui-pro-2.5.1, I noticed the Query/Match functionality in a Mock Response test step. This Query/Match functionality I need for some of my test cases where the service under test calls the same operation on a service more than once and expects different data back in the response. To make the situation clearer, the service under test is a synchronous web service, so the Mock Response test steps are started before the SOAP request is send (Start Step property in the Mock Response test step is set).
In this test case setup I stumbled upon two problems, which I could trace back to bugs in the Soap-ui source code.
The first problem is that though specifying a correct Query/Match for a Mock Response, no response is sent to back.
The source of the problem lies in the initTestMockResponse method of the WsdlMockResponseTestStep class. There if a query/match is specified, a new WsdlMockResponse instance is created with a generated unique name. The unique name is later used by the QueryMatchMockOperationDispatcher class to obtain the WsdlMockResponse instance, but before that happens the unique name is reset in the current code when the MockResponseConfig is copied from the test step to the WsdlMockResponse instance.
To fix the problem the above line of code can be replaced by the following code.
The second problem is that only the first Mock Response test step can return a response.
The source of this problem is that each Mock Response test step creates its own MockRunner. The MockEngine class iterates over the list of MockRunner instances till it receives a result, but the dispatchRequest method of a MockRunner class will always return a result (a success- or failureresponse). So the dispatchRequest method of the other MockRunner instances will never be called and thus the responses of their Mock Response test steps will never be sent.
To fix this problem some changes are necessary.
1.) The dispatchPostRequest method of the WsdlMockRunner class may not return a soap-fault result in case of a dispatchException.
2a.) The content of a HttpRequest instance must be resettable so that the next MockRunner instance can reread the HttpRequest instances content. Therefore I added a new resettable input stream inner class to the MockEngine class, from which an instance is returned in the getInputStream method of the SoapUIHttpConnection class.
2b.) The content of a HttpRequest instance must be reset after the content is read, so that subsequent reads are possible. Therefore I made the following change to the readRequestContent method of the WsdlMockRequest class.
3.) The looping over the MockRunners in the MockEngine class (ServerHandler inner class) needs to be adjusted so that a DispatchException is only handled if no (successful) result is available.
With these patches to the soap-ui 2.5.1 source code, I was able to run my testcase of a synchronous web service with two Mock Response test steps on the same interface and operation with a Query/Match specified on both.
I hope this problem can be addressed in the next release of soap-ui and that my analysis of the problem helps. Note that I only tested the described patch with my specific case. I made no load tests for instance. Further the patch could lead to other problems that I’m aware of and may not fit in the design of soap-ui. There could also be better ways to fix the problems.
Regards,
Arjen Wassink
In this test case setup I stumbled upon two problems, which I could trace back to bugs in the Soap-ui source code.
The first problem is that though specifying a correct Query/Match for a Mock Response, no response is sent to back.
The source of the problem lies in the initTestMockResponse method of the WsdlMockResponseTestStep class. There if a query/match is specified, a new WsdlMockResponse instance is created with a generated unique name. The unique name is later used by the QueryMatchMockOperationDispatcher class to obtain the WsdlMockResponse instance, but before that happens the unique name is reset in the current code when the MockResponseConfig is copied from the test step to the WsdlMockResponse instance.
testMockResponse.setConfig( mockResponse.getConfig() );
To fix the problem the above line of code can be replaced by the following code.
MockResponseConfig config = mockResponse.getConfig();
config.setName(testMockResponse.getName());
testMockResponse.setConfig(config);
The second problem is that only the first Mock Response test step can return a response.
The source of this problem is that each Mock Response test step creates its own MockRunner. The MockEngine class iterates over the list of MockRunner instances till it receives a result, but the dispatchRequest method of a MockRunner class will always return a result (a success- or failureresponse). So the dispatchRequest method of the other MockRunner instances will never be called and thus the responses of their Mock Response test steps will never be sent.
To fix this problem some changes are necessary.
1.) The dispatchPostRequest method of the WsdlMockRunner class may not return a soap-fault result in case of a dispatchException.
// try
// {
result = mockOperation.dispatchRequest( mockRequest );
// }
// catch( DispatchException e )
// {
// result = new WsdlMockResult( mockRequest );
//
// String fault = SoapMessageBuilder.buildFault( "Server", e.getMessage(), mockRequest.getSoapVersion() );
// result.setResponseContent( fault );
// result.setMockOperation( mockOperation );
//
// mockRequest.getHttpResponse().getWriter().write( fault );
// }
2a.) The content of a HttpRequest instance must be resettable so that the next MockRunner instance can reread the HttpRequest instances content. Therefore I added a new resettable input stream inner class to the MockEngine class, from which an instance is returned in the getInputStream method of the SoapUIHttpConnection class.
private class SoapUIHttpConnection extends HttpConnection
{
private CapturingServletInputStream capturingServletInputStream;
private BufferedServletInputStream bufferedServletInputStream;
private MockEngine.CapturingServletOutputStream capturingServletOutputStream;
public SoapUIHttpConnection( Connector connector, EndPoint endPoint, Server server )
{
super( connector, endPoint, server );
}
@Override
public ServletInputStream getInputStream()
{
if( capturingServletInputStream == null )
{
capturingServletInputStream = new CapturingServletInputStream( super.getInputStream());
bufferedServletInputStream = new BufferedServletInputStream(capturingServletInputStream);
}
return bufferedServletInputStream;
}
@Override
public ServletOutputStream getOutputStream()
{
if( capturingServletOutputStream == null )
{
capturingServletOutputStream = new CapturingServletOutputStream( super.getOutputStream() );
}
return capturingServletOutputStream;
}
}
private class BufferedServletInputStream extends ServletInputStream {
private InputStream source = null;
private byte [] data = null;
private InputStream buffer1 = null;
public BufferedServletInputStream(InputStream is) {
super();
source = is;
}
public InputStream getBuffer() throws IOException {
if (source.available() > 0){
// New request content available
data = null;
}
if (data == null){
ByteArrayOutputStream out = Tools.readAll( source, Tools.READ_ALL );
data = out.toByteArray();
}
if (buffer1 == null){
buffer1 = new ByteArrayInputStream(data);
}
return buffer1;
}
public int read()
throws IOException
{
int i = getBuffer().read();
return i;
}
public int readLine(byte[] b, int off, int len) throws IOException {
if (len <= 0) {
return 0;
}
int count = 0, c;
while ((c = read()) != -1) {
b[off++] = (byte)c;
count++;
if (c == '\n' || count == len) {
break;
}
}
return count > 0 ? count : -1;
}
public int read( byte[] b )
throws IOException
{
int i = getBuffer().read( b );
return i;
}
public int read( byte[] b, int off, int len )
throws IOException
{
int result = getBuffer().read( b, off, len );
return result;
}
public long skip( long n )
throws IOException
{
return getBuffer().skip( n );
}
public int available()
throws IOException
{
return getBuffer().available();
}
public void close()
throws IOException
{
getBuffer().close();
}
public void mark( int readlimit )
{
//buffer.mark( readlimit );
}
public boolean markSupported()
{
return false;
}
public void reset()
throws IOException
{
buffer1 = null;
}
}
2b.) The content of a HttpRequest instance must be reset after the content is read, so that subsequent reads are possible. Therefore I made the following change to the readRequestContent method of the WsdlMockRequest class.
InputStream is = request.getInputStream();
ByteArrayOutputStream out = Tools.readAll( request.getInputStream(), Tools.READ_ALL );
byte [] data = out.toByteArray();
is.reset();
3.) The looping over the MockRunners in the MockEngine class (ServerHandler inner class) needs to be adjusted so that a DispatchException is only handled if no (successful) result is available.
try
{
DispatchException ex = null;
MockResult result = null;
for( MockRunner wsdlMockRunner : wsdlMockRunners )
{
try
{
result = wsdlMockRunner.dispatchRequest( request, response );
if( result != null )
result.finish();
// if we get here, we got dispatched.
break;
}
catch( DispatchException e )
{
ex = e;
}
}
if( result == null && ex != null )
throw ex;
}
With these patches to the soap-ui 2.5.1 source code, I was able to run my testcase of a synchronous web service with two Mock Response test steps on the same interface and operation with a Query/Match specified on both.
I hope this problem can be addressed in the next release of soap-ui and that my analysis of the problem helps. Note that I only tested the described patch with my specific case. I made no load tests for instance. Further the patch could lead to other problems that I’m aware of and may not fit in the design of soap-ui. There could also be better ways to fix the problems.
Regards,
Arjen Wassink