13

I'm trying to use py.test to test some code that does various LDAP searches, and modifications.

I'm using pytest-mock, but I'm having trouble understanding how to mock out the creation of the an LDAP object, and control what it returns when a search_s() is called on the mocked object.

I thought this would do what I wanted, but the test fails, the count shows the generator function find_users() never yields anything.

import pytest
# Here is some code to simply test mocking out ldap.initialize(), and
# controlling the return value from calls to search_s()
import ldap

def find_users(ldap_url, admin_user, admin_password, userbase):
    lobj = ldap.initialize(ldap_url)
    lobj.simple_bind_s(admin_user, admin_password)
    for i in lobj.search_s(userbase, ldap.SCOPE_SUBTREE, '*'):
        yield i[1]['uid'][0]

class TestMocking:

    @pytest.fixture()
    def no_ldap(self, mocker):
        return mocker.patch('ldap.initialize')


    def test_ad_one_user(self, no_ldap):
        # try and modify how search_s() would return
        no_ldap.search_s.return_value = ('', {'uid': ['happy times']})
        count = 0 
        for i in find_users('', '', '', ''):
            count += 1
            assert i=='happy times'
        assert count == 1
Rémy
  • 114
  • 11
Geoff Crompton
  • 440
  • 1
  • 5
  • 15

2 Answers2

7

You can just use patch directly (and something was off about your structure):

from mock import patch, Mock
import pytest
# Here is some code to simply test mocking out ldap.initialize(), and
# controlling the return value from calls to search_s()
import ldap

def find_users(ldap_url, admin_user, admin_password, userbase):
    lobj = ldap.initialize(ldap_url)
    lobj.simple_bind_s(admin_user, admin_password)
    for i in lobj.search_s(userbase, ldap.SCOPE_SUBTREE, '*'):
        yield i[1]['uid'][0]

class TestMocking:

    @patch('ldap.initialize')
    def test_ad_one_user(self, no_ldap):
        # try and modify how search_s() would return
        data = [('', {'uid': ['happy times']})]
        search_s = Mock(return_value=data)
        no_ldap.return_value = Mock(search_s=search_s)
        count = 0
        for i in find_users('', '', '', ''):
            count += 1
            assert i=='happy times'
        assert count == 1
salparadise
  • 5,699
  • 1
  • 26
  • 32
  • Thanks for the answer @salparadise. This one works, but I'm still confused, because I thought the py.test "way" was to use fixtures, so you could re-use them across tests. – Geoff Crompton Dec 19 '16 at 22:02
  • @GeoffCrompton depends on your goal. fixtures allows scoping per module or test session, and by default only on a function (so exactly as this is doing), however you can also use unittest.TestCase and use a `setUp` function to initialize your patches for all the unittests in the class. – salparadise Dec 19 '16 at 23:55
  • 1
    `You can just use patch directly` - is this documented somewhere? – Dušan Maďar Mar 14 '17 at 21:00
  • @dm295 is this what you are after https://docs.python.org/3/library/unittest.mock.html#nesting-patch-decorators – salparadise Mar 14 '17 at 22:10
  • @salparadise thanks. I knew how to use `patch` with `unittests`. But I didn't know that it's possible to use with `pytest` as well. – Dušan Maďar Mar 15 '17 at 08:06
3

I think you may be getting confused between https://docs.python.org/3/library/unittest.mock.html and pytest monkey patch. I don't think both behave in the same way.

You can make it work using mock patch (https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch)

@pytest.fixture()
def no_ldap(self):
    patcher = mock.patch('ldap.initialize')
    patcher.start()
    yield patcher
    patcher.stop()
  
Manfred
  • 5,320
  • 3
  • 35
  • 29
apatniv
  • 1,771
  • 10
  • 13
  • 1
    Nice. See also [this answer](https://stackoverflow.com/a/50048692/456550) for a way to do the same thing by using patch() as a context manager. – Christian Long Oct 09 '18 at 21:52