pythonunit-testingmockingzeep

Does someone have an example of Unit-Testing a SOAP API with Python Zeep and Mock?


I'm building a Python app that accesses a 3rd party SOAP API with Python-zeep. I want to implement some unit-tests using mocked responses as I don't always have a live server to run my tests against.

I'm new to unit-testing and not quite sure where to start. I've seen examples of using mock with the requests library, but not really sure how to translate this into Zeep.

On one of my Models I have a method to get all DevicePools from a SOAP API. Here's an excerpt of the relevant code (I use a helper method to provide the service object as I plan on using this in many other methods).

# Get Zeep Service Object to make AXL API calls
service = get_axl_client(self)

# Get list of all DevicePools present in the cluster
resp = service.listDevicePool(searchCriteria={'name': '%'},
                              returnedTags={'name': '', 'uuid': ''})

I want to mock the resp object, however this is of type zeep.objects.ListDevicePoolRes (a dynamic type based on the parsing of the WSDL) and I can't just instantiate an object with static values.

Maybe I'm on the wrong track here and would have to go a bit deeper and actually mock some internals of the zeep library and replace the requests response before zeep parses the XML?

If someone had an example of something similar it would be greatly appreciated.


Solution

  • After looking through the Python Zeep source code, I found some examples of tests using the requests-mock library which I was able to work into my solution. Here's a working example in case anyone else is trying to do something similar.

    I'm not doing any assertions in this example as I first wanted to prove the concept that I could get zeep to parse a mock response correctly.

    # -*- coding: utf-8 -*-
    
    from zeep import Client
    from zeep.cache import SqliteCache
    from zeep.transports import Transport
    from zeep.exceptions import Fault
    from zeep.plugins import HistoryPlugin
    from requests import Session
    from requests.auth import HTTPBasicAuth
    from urllib3 import disable_warnings
    from urllib3.exceptions import InsecureRequestWarning
    from lxml import etree
    import requests_mock
    
    disable_warnings(InsecureRequestWarning)
    
    username = 'administrator'
    password = 'password'
    host = 'cucm-pub'
    
    wsdl = 'file://C:/path/to/axlsqltoolkit/schema/current/AXLAPI.wsdl'
    location = 'https://{host}:8443/axl/'.format(host=host)
    binding = "{http://www.cisco.com/AXLAPIService/}AXLAPIBinding"
    
    session = Session()
    session.verify = False
    session.auth = HTTPBasicAuth(username, password)
    
    transport = Transport(cache=SqliteCache(), session=session, timeout=20)
    history = HistoryPlugin()
    client = Client(wsdl=wsdl, transport=transport, plugins=[history])
    service = client.create_service(binding, location)
    
    def test_listPhone():
        text = """<?xml version="1.0" ?>
    <soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
      <soapenv:Body>
        <ns:listDevicePoolResponse xmlns:ns="http://www.cisco.com/AXL/API/11.5">
          <return>
            <devicePool uuid="{1B1B9EB6-7803-11D3-BDF0-00108302EAD1}">
              <name>AU_PER_DP</name>
            </devicePool>
            <devicePool uuid="{BEF5605B-1DB0-4157-0176-6C07814C47AE}">
              <name>DE_BLN_DP</name>
            </devicePool>
            <devicePool uuid="{B4C65CAB-86CB-30FB-91BE-6F6E712CACB9}">
              <name>US_NYC_DP</name>
            </devicePool>
          </return>
        </ns:listDevicePoolResponse>
      </soapenv:Body>
    </soapenv:Envelope>
        """
    
    
        with requests_mock.Mocker() as m:
            m.post(location, text=text)
            resp = service.listDevicePool(searchCriteria={'name': '%'},
                                          returnedTags={'name': '',
                                                        'uuid': ''})
    
        return resp
    
    test_listPhone()
    

    This then gives me the following result (have manually removed all the attributes with "none" that Zeep includes for brevity):

    {
        'return': {
            'devicePool': [
                {
                    'name': 'AU_PER_DP',
                    'uuid': '{1B1B9EB6-7803-11D3-BDF0-00108302EAD1}'
                },
                {
                    'name': 'DE_BLN_DP',
                    'uuid': '{BEF5605B-1DB0-4157-0176-6C07814C47AE}'
                },
                {
                    'name': 'US_NYC_DP',
                    'uuid': '{B4C65CAB-86CB-30FB-91BE-6F6E712CACB9}'
                }
            ]
        },
        'sequence': None
    }