List of function I want to Mock. Mocking function. Pytest

84 views Asked by At

I have a list of functions that return Boolean values. It's all called like this:

for f in functions:
    if f():
        pass

I am writing tests and I want to mock the result of those functions

@pytest.fixture
def is_first_func_mock(mocker):
    mock = mocker.patch.object(SomeClass, 'first_func')
    return mock

and then somewhere in the test I pass this fixture and write is_first_func_mock.return_value = True or something like that

But since I'm not calling the function directly in the code, and since I indicated it above, the mock does not work.

I found a solution in Stackoverflow: I can call functions like that:

if getattr(funcs_module, func.__name__)():
    pass

But I don't like it and I don't want to change the loop

Update:

def some_name(fucntions: list[callable]):
    def inner(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            #some code
            for f in functions:
                if f():
                    return await func(*args, **kwargs)
            raise SomePermission

        return wrapper

    return inner
1

There are 1 answers

0
quamrana On BEST ANSWER

I haven't been able to find a solution, but here is a workaround:

from functools import wraps

def has_any_permissions(get_functions):
    def inner(func):
        @wraps(func)
        async def wrapper(*args, **kwargs):
            # some code
            funcs = get_functions()
            if any(f() for f in funcs):
                return await func(*args, **kwargs)
            raise SomePermission()

        return wrapper
    return inner

def is_admin():
    return False

def is_users_moderator():
    return False

def admin_and_users_moderator():
    return [is_admin, is_users_moderator]

@has_any_permissions(admin_and_users_moderator)
def about_user():
    return 'user'   # just for demonstration purposes

I have had to introduce some indirection where the decorator does not bind the actual list of functions at decoration time, but instead supplies a function which, when called inside wrapper() gets the functions each time the target could be called.

The corresponding unit test might look like this:

import unittest
from unittest.mock import patch

from mockable_functions import about_user

class Test(unittest.TestCase):
    def test_plain_function_raises(self):
        self.assertRaises(SomePermission, about_user)

    @patch('mockable_functions.is_admin')
    def test_func(self, is_admin_mock):
        is_admin_mock.return_value = True
        self.assertEqual(about_user(), 'user', 'about_user returns user')

This unit test shows that about_user() raises an error when called normally, but when the is_admin function is patched, then the real function gets called (and returns a dummy value)