Skip to content

Latest commit

 

History

History
180 lines (128 loc) · 6.86 KB

unittest.rst

File metadata and controls

180 lines (128 loc) · 6.86 KB

ldap_faker

Using ldap_faker with unittest

Most of the purpose of python-ldap-faker is to make automated testing of code that uses python-ldap easier.

To this end, python-ldap-faker provides :pyLDAPFakerMixin, a mixin class for :pyunittest.TestCase which handles all the hard work of patching and instrumenting the appropriate python-ldap functions, objects and methods.

:pyLDAPFakerMixin will do the following things for you:

  • Read data from JSON fixture files to populate one or more :pyObjectStore objects (our fake LDAP server class)
  • Associate those :pyObjectStore objects with particular LDAP URIs
  • Patch :pyldap.initialize to return :pyFakeLDAPObject objects configured with the appropriate :pyObjectStore for the LDAP URI passed into :pyFakeLDAP.initialize

Configuring your LDAPFakerMixin TestCase

We need to set two class attributes on :pyLDAPFakerMixin in order for it to properly set up your tests:

  • :pyLDAPFakerMixin.ldap_modules: The list of your code's modules in which to patch :pyldap.initialize, :pyldap.set_option and :pyldap.get_option`
  • :pyLDAPFakerMixin.ldap_fixtures: A list of JSON fixture files with which to create the :pyObjectStore objects

LDAPFakerMixin.ldap_modules

:pyLDAPFakerMixin uses :pyunittest.mock.patch to patch your code so that it uses our fake versions of :pyldap.initialize, :pyldap.set_option and :pyldap.get_option instead of the real one. The way patch works is that it must apply the patch within the context of your module that does import ldap, not within the ldap module itself. Thus, to make :pyLDAPFakerMixin work for you, you must list all the modules for code under test in which you do import ldap.

To list all the modules in which the code under test does import ldap, use the :pyLDAPFakerMixin.ldap_modules class attribute.

For example, if you have a class MyLDAPUsingClass in the module myapp.myldapstuff, and you do import ldap in myapp.myldapstuff, for instance:

import ldap

class MyLDAPUsingClass:

    def connect(self, uid: str, password: str):
        self.conn = ldap.initialize('ldap://server')
        self.conn.set_option(ldap.OPT_X_TLS_NEWCTX, 0)
        self.conn.start_tls_s()
        self.conn.simple_bind_s(
          f'uid={uid},ou=bar,o=baz,c=country',
          'the password'
        )

To test this code, you would use this for ldap_modules:

import unittest
from ldap_faker import LDAPFakerMixin

from myapp.myldapstuff import MyLDAPUsingClass

class TestMyLDAPUsingCLass(LDAPFakerMixin, unittest.TestCase):

    ldap_modules = ['myapp.myldapstuff']

LDAPFakerMixin.ldap_fixtures

In order to effectively test your python-ldap using code, you'll need to populate an :pyLDAPServerFactory one or more :pyObjectStore objects bound to LDAP URIs. We use :pyLDAPFakerMixin.ldap_fixtures to declare file paths to fixture files to use to populate those :pyObjectClass objects.

  • Fixture files are JSON files in the format described in File format for ObjectStore.load_objects.
  • File paths are either absolute paths or are treated as relative to the folder in which your TestCase resides.
  • Fixtures are loaded into the :pyLDAPServerFactory once per :pyunittest.TestCase via the :pyunittest.TestCase.setUpClass classmethod.

You can configure your :pyLDAPFakerMixin to use fixtures one of two ways:

  • Use a single default fixture that will be used no matter which LDAP URI is passed to :pyFakeLDAP.initialize
  • Bind each fixture to specific a LDAP URI. This allows you simulate talking to several different LDAP servers.

Note

When binding fixtures to particular LDAP URIs, if your tries to use :pyFakeLDAP.initialize with an LDAP URI that was not explicitly configured, python-ldap-faker will raise :pyldap.SERVER_DOWN

This form sets up one default fixture:

import unittest
from ldap_faker import LDAPFakerMixin

from myapp.myldapstuff import MyLDAPUsingClass

class TestMyLDAPUsingCLass(LDAPFakerMixin, unittest.TestCase):

    ldap_fixtures = 'objects.json'

This form binds fixtures to LDAP URIs:

import unittest
from ldap_faker import LDAPFakerMixin

from myapp.myldapstuff import MyLDAPUsingClass

class TestMyLDAPUsingCLass(LDAPFakerMixin, unittest.TestCase):

    ldap_fixtures = [
      ('server1.json', 'ldap://server1.example.com'),
      ('server2.json', 'ldap://server2.example.com')
    ]

Test isolation

Each test method on your :pyunittest.TestCase will get a fresh, unaltered copy of the fixture data, and connections, call histories, options set from previous test methods will be cleared.

Test support offered by LDAPFakerMixin

For each test you run, your test will have access to the :pyFakeLDAP instance used for that test through the :pyLDAPFakerMixin.fake_ldap instance attribute. Each test gets a fresh :pyFakeLDAP instance.

Note

For detailed information on any of the below, see the api.

Some things to know about your :pyFakeLDAP instance:

  • :pyFakeLDAP.connections lists all the :pyFakeLDAPObject connections created during your test method, in the order they were made. One such object is created each time :pyFakeLDAP.initialize is called by your code.
  • :pyFakeLDAP.options is a :pyOptionStore object that records all the global LDAP options set during your test
  • :pyFakeLDAP.calls is a :pyCallHistory object that records calls (with arguments) to :pyFakeLDAP.initialize, :pyFakeLDAP.set_option, :pyFakeLDAP.get_option

Some things to know about the :pyFakeLDAPObject objects in :pyFakeLDAP.connections:

  • :pyFakeLDAPObject.uri is the LDAP URI requested
  • :pyFakeLDAPObject.store is our :pyObjectStore copy
  • :pyFakeLDAP.options is a :pyOptionStore object that records all the LDAP options set on this connection during your test method
  • :pyFakeLDAPObject.calls is a :pyCallHistory that records all python-ldap api calls (with arguments) that your code made to this FakeLDAPObject
  • :pyFakeLDAPObject.bound_dn is the dn of the user bound via simple_bind_s, if any. If this is None, we did anonymous binding.
  • :pyFakeLDAPObject.tls_enabled will be set to True if start_tls_s was used on this connection