The goal of mockldap is to provide a mock instance of LDAPObject in response to any call to ldap.initialize. In the general case, you would register return values for all LDAPObject calls that you expect the code under test to make. Your assertions would then verify that the tested code behaved correctly given this set of return values from the LDAP APIs.

As a convenience, the mock LDAPObject isn’t just a dumb mock object. The typical way to use mockldap is to provide some static directory content and then let LDAPObject generate real return values. This will only work for simple LDAP operations–this obviously isn’t a complete Python LDAP server implementation–but those simple operations tend to cover a lot of cases.

This example integrates with unittest.TestCase by explicitly starting and stopping the MockLdap object. You can also use these objects as context managers, if that’s more convenient.


import unittest
import ldap

from mockldap import MockLdap

class MyTestCase(unittest.TestCase):
    A simple test case showing off some of the basic features of mockldap.
    top = ('o=test', {'o': ['test']})
    example = ('ou=example,o=test', {'ou': ['example']})
    other = ('ou=other,o=test', {'ou': ['other']})
    manager = ('cn=manager,ou=example,o=test', {'cn': ['manager'], 'userPassword': ['ldaptest']})
    alice = ('cn=alice,ou=example,o=test', {'cn': ['alice'], 'userPassword': ['alicepw']})
    bob = ('cn=bob,ou=other,o=test', {'cn': ['bob'], 'userPassword': ['bobpw']})

    # This is the content of our mock LDAP directory. It takes the form
    # {dn: {attr: [value, ...], ...}, ...}.
    directory = dict([top, example, other, manager, alice, bob])

    def setUpClass(cls):
        # We only need to create the MockLdap instance once. The content we
        # pass in will be used for all LDAP connections.
        cls.mockldap = MockLdap(cls.directory)

    def tearDownClass(cls):
        del cls.mockldap

    def setUp(self):
        # Patch ldap.initialize
        self.ldapobj = self.mockldap['ldap://localhost/']

    def tearDown(self):
        # Stop patching ldap.initialize and reset state.
        del self.ldapobj

    def test_some_ldap(self):
        Some LDAP operations, including binds and simple searches, can be
        results = _do_simple_ldap_search()

        self.assertEquals(self.ldapobj.methods_called(), ['initialize', 'simple_bind_s', 'search_s'])
        self.assertEquals(sorted(results), sorted([self.manager, self.alice]))

    def test_complex_search(self):
        Some LDAP operations, such as complex searches, are not implemented.
        If you're doing anything nontrivial, you have to set an explicit
        return value for a set of parameters.
        self.ldapobj.search_s.seed('o=test', ldap.SCOPE_SUBTREE, '(|(cn=b*b)(cn=a*e))')([self.alice, self.bob])

        results = _do_complex_ldap_search()

        self.assertEquals(self.ldapobj.methods_called(), ['initialize', 'simple_bind_s', 'search_s'])
        self.assertEquals(sorted(results), sorted([self.alice, self.bob]))

def _do_simple_ldap_search():
    conn = ldap.initialize('ldap://localhost/')
    conn.simple_bind_s('cn=alice,ou=example,o=test', 'alicepw')
    results = conn.search_s('ou=example,o=test', ldap.SCOPE_ONELEVEL, '(cn=*)')

    return results

def _do_complex_ldap_search():
    conn = ldap.initialize('ldap://localhost/')
    conn.simple_bind_s('cn=alice,ou=example,o=test', 'alicepw')
    results = conn.search_s('o=test', ldap.SCOPE_SUBTREE, '(|(cn=b*b)(cn=a*e))')

    return results