Forum Discussion

a_wasink's avatar
a_wasink
Occasional Contributor
15 years ago

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.

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

10 Replies

  • SmartBear_Suppo's avatar
    SmartBear_Suppo
    SmartBear Alumni (Retired)
    Hi Arjen,

    Thanks for your fantastic effort, I'll try to review this until tomorrow and will let you know what has been merged/changed/etc.

    regards!

    /Ole
    eviware.com
  • a_wasink's avatar
    a_wasink
    Occasional Contributor
    Hi Ole,

    Thanks for the quick reply.

    For your information: I checked the soap-ui-pro 2.5.2 nightly build from 20-05-2009 already. The problem still exisited in this version and I could not find any changes related to the problem.

    Regards,

    Arjen
  • SmartBear_Suppo's avatar
    SmartBear_Suppo
    SmartBear Alumni (Retired)
    Hi,

    yes.. these improvements will be in the 3.0 beta nightly builds for now..

    I am having some issues with this though.. I'll let you know when its in..

    regards!

    /Ole
    eviware.com
  • a_wasink's avatar
    a_wasink
    Occasional Contributor
    There is another code change that is necessary to get it working. I thought the changed had no impact but as I was reverting obsolete changes the test case wasn’t running anymore. The code I changed is in the selectMockResponse method of the QueryMatchMockOperationDispatcher class. See below. The commented line is the original code replaced.

                    WsdlMockResponse resp = null;
                    for (MockResponse mockResponse : this.getMockOperation().getMockResponses()) {
                        if (query.getResponse().equals(mockResponse.getName())){
                        resp = (WsdlMockResponse) mockResponse;
                        }
    }
    //                return  getMockOperation().getMockResponseByName( query.getResponse() );
                    if (resp != null){
                        return resp;
                    }

    There are two problems requiring this change:

    1.) Without the change the WsdlMockResponseTestStep fails with a timeout although a request is received and successfully dispatched. The problem lies therein that there are two MockResponse objects assigned to the MockOperation object with the same name. The first object is the MockResponse object set as mockReponse in the WsdlMockResponseTestStep. The second one is the MockResponse object newly created in the method and set as testMockResponse in the WsdlMockResponseTestStep. This second one is the one to use and is returned by the code change.
    Note that the actual problem is that no deep copy of the MockResponseConfig is made in the initTestMockResponse method of the WsdlMockResponseTestStep class.

    Further, it is unclear to me why a new MockResponse object is created in the initTestMockResponse method in case of a Query/Match. Also I don’t understand why a generated unique name is used in the Query object to address the MockResponse object and not a reference.

    2.) Having fixed point 1, subsequent runs of the test case fail. This is because no Mock Response is found by the dispatcher. This is caused by the fact the Query objects of previous test runs are not removed from the query list of the dispatcher. These ‘old’ Query objects have no matching MockResponse object anymore because they are removed. Thus the first Query object processed in the loop will return null as method result. This is easily worked around by only returning a result when the result is not null.

    Note that the missing removal of the Query objects is causing a memory leak in soap-ui.


    Regards,

    Arjen
  • a_wasink's avatar
    a_wasink
    Occasional Contributor
    Hi Ole,

    I adopted your proposed change and found out that there is a problem with that. The problem is that subsequent MockResponse dispatchers are not evaluated upon request entry because the first MockReponse dispatcher will, in case of no match, always return the default response.

    This problem can easily be fixed by adding the following of code to the initTestMockResponse method of the WsdlMockResponseTestStep.

            mockOperation.setDefaultResponse(null);



    Furthermore I noticed (generally for MockResponse teststeps with a Query/Match) the following two problems;
    1) the received request is not shown in the UI
    2) though a request assertion correctly fails and the failure is shown in the UI, the testrun does not fail.


    The cause of these problems is that in case of Query/Match a new WsdlMockResponse object is created though the UI is bound to the existing WsdlMockResponse object. A possible fix for this problem is to add the following line of code to the internalRun method of the WsdlMockResponseTestStep class (after the result.stopTimer() lines).

             mockResponse.setMockResult(testMockResponse.getMockResult());



    Regards,

    Arjen
  • SmartBear_Suppo's avatar
    SmartBear_Suppo
    SmartBear Alumni (Retired)
    Hi Arjen,

    sorry for the long delay on this, I'm working on these issues now and hope to have them in the nightly build as you have suggested, thanks for your help!

    regards,

    /Ole
    eviware.com
  • Hi guys,
    Any news on this problem. I'm still seeing it in 3.0. Is there any build that I can use to test the fixes?

    Regards
    Sergio
  • SmartBear_Suppo's avatar
    SmartBear_Suppo
    SmartBear Alumni (Retired)
    Hi!

    finally we have made an effort to get this fixed, the latest changes are in the upcoming nightly build of 4.0 beta, please give it a go and let us know how it works out..

    regards!

    /Ole
    eviware.com