This is a continuation of the SO question asked here but with a more complicated pattern than originally requested. My main intention is to try to mock an API call based on values passed to its caller. The API call has no idea of the values passed to its caller but needs to provide the correct behavior so that the caller can be tested fully. I am using time to determine which behavior I want when I want it.
Given a an object:
# some_object.py
from some_import import someApiCall
class SomeObject():
def someFunction(time, a, b, c):
apiReturnA = someApiCall(a)
returnB = b + 1
apiReturnC = someApiCall(c)
return [apiReturnA, returnB, apiReturnC]
that is created by another object with entry point code:
# some_main.py
import some_object
class myMainObject():
def entry_point(self, time):
someObj = some_object.SomeObject()
if 'yesterday' == time:
(a, b, c) = (1, 1, 1)
elif 'today' == time:
(a, b, c) = (2, 2, 2)
elif 'later' == time:
(a, b, c) = (3, 3, 3)
elif 'tomorrow' == time:
(a, b, c) = (4, 4, 4)
else:
return "ERROR"
return someObj.someFunction(time, a, b, c)
how can I get someApiCall to change based on the time argument?
# some_import.py
def someApiCall(var):
print("I'm not mocked! I'm slow, random and hard to test")
return var + 2
Here is an example test case
# test_example.py
import some_main
def amend_someApiCall_yesterday(var):
# Reimplement api.someApiCall
return var * 2
def amend_someApiCall_today(var):
# Reimplement api.someApiCall
return var * 3
def amend_someApiCall_later(var):
# Just wrap around api.someApiCall. Call the original function afterwards. Here we can also put
# some conditionals e.g. only call the original someApiCall if the var is an even number.
import some_import
var *= 4
return some_import.someApiCall(var)
def someObject_decorator_patch(someFunction, mocker, *args):
def wrapper(time, a, b, c):
# If x imports y.z and we want to patch the calls to z, then we have to patch x.z. Patching
# y.z would still retain the original value of x.z thus still calling the original
# functionality. Thus here, we would be patching src.someApiCall and not api.someApiCall.
if time == "yesterday":
mocker.patch("some_object.someApiCall", side_effect=amend_someApiCall_yesterday)
elif time == "today":
mocker.patch("some_object.someApiCall", side_effect=amend_someApiCall_today)
elif time == "later":
mocker.patch("some_object.someApiCall", side_effect=amend_someApiCall_later)
elif time == "tomorrow":
mocker.patch("src.someApiCall", return_value=0)
else:
# Use the original api.someApiCall
pass
return someFunction(time, a, b, c)
return wrapper
def test_some_main(mocker):
results = 0
uut = some_main.myMainObject()
times = ['yesterday', 'today', 'later', 'tomorrow']
mocker.patch.object(some_main.some_object.SomeObject, 'someFunction', someObject_decorator_patch)
for time in times:
results = uut.entry_point(time)
print(results)
assert 0 != results
The test case doesn't get the result I want (it returns a function pointer).
Here is an improvised solution of https://stackoverflow.com/a/67498948/11043825 without using decorators.
As already pointed out, we still need to intercept the calls to the function that accepts the time argument which indicates how someApiCall would behave, which is either entry_point or someFunction. Here we would intercept someFunction.
Instead of implementing python decorator on someFunction which then needs to call that explicitly created decorated function, here we would amend (well this still follows the decorator design pattern) the someFunction in-place and make it available to the rest of the source code calls without explicitly changing the call to the decorated function. This is like an in-place replacement of the original functionalities, where we would replace (or rather wrap around) the original functionality with an updated one which would perform an assessment of the time before calling the original functionality.
Also for your reference, I solved it for 2 types of functions, a class method src.SomeClass.someFunction and a global function src.someFunction2.
./_main.py
import src
class MyMainClass:
def __init__(self):
self.var = 0
def entry_point(self, time):
someObj = src.SomeClass()
self.var += 1
if self.var >= 10:
self.var = 0
ret = f'\n[1]entry_point({time})-->{someObj.someFunction(time, self.var)}'
self.var += 1
ret += f'\n[2]entry_point({time})-->{src.someFunction2(time, self.var)}'
return ret
./src.py
class SomeClass:
def someFunction(self, time, var):
return f'someFunction({time},{var})-->{someSloowApiCall(var)}'
def someFunction2(time, var):
return f'someFunction2({time},{var})-->{someSloowApiCall2(var)}'
./api.py
def someSloowApiCall(var):
return f'someSloowApiCall({var})-->{special_message(var)}'
def someSloowApiCall2(var):
return f'someSloowApiCall2({var})-->{special_message(var)}'
def special_message(var):
special_message = "I'm not mocked! I'm slow, random and hard to test"
if var > 10:
special_message = "I'm mocked! I'm not slow, random or hard to test"
return special_message
./test_main.py
import _main, pytest, api
def amend_someApiCall_yesterday(var):
# Reimplement api.someSloowApiCall
return f'amend_someApiCall_yesterday({var})'
def amend_someApiCall_today(var):
# Reimplement api.someSloowApiCall
return f'amend_someApiCall_today({var})'
def amend_someApiCall_later(var):
# Just wrap around api.someSloowApiCall. Call the original function afterwards. Here we can also put
# some conditionals e.g. only call the original someSloowApiCall if the var is an even number.
return f'amend_someApiCall_later({var})-->{api.someSloowApiCall(var+10)}'
def amend_someApiCall_later2(var):
# Just wrap around api.someSloowApiCall2. Call the original function afterwards. Here we can also put
# some conditionals e.g. only call the original someSloowApiCall2 if the var is an even number.
return f'amend_someApiCall_later2({var})-->{api.someSloowApiCall2(var+10)}'
def get_amended_someFunction(mocker, original_func):
def amend_someFunction(self, time, var):
if time == "yesterday":
mocker.patch("_main.src.someSloowApiCall", amend_someApiCall_yesterday)
# or
# src.someSloowApiCall = amend_someApiCall_yesterday
elif time == "today":
mocker.patch("_main.src.someSloowApiCall", amend_someApiCall_today)
# or
# src.someSloowApiCall = amend_someApiCall_today
elif time == "later":
mocker.patch("_main.src.someSloowApiCall", amend_someApiCall_later)
# or
# src.someSloowApiCall = amend_someApiCall_later
elif time == "tomorrow":
mocker.patch("_main.src.someSloowApiCall", lambda var: f'lambda({var})')
# or
# src.someSloowApiCall = lambda var: 0
else:
pass
# or
# src.someSloowApiCall = someSloowApiCall
return original_func(self, time, var)
return amend_someFunction
def get_amended_someFunction2(mocker, original_func):
def amend_someFunction2(time, var):
if time == "yesterday":
mocker.patch("_main.src.someSloowApiCall2", amend_someApiCall_yesterday)
# or
# src.someSloowApiCall2 = amend_someApiCall_yesterday
elif time == "today":
mocker.patch("_main.src.someSloowApiCall2", amend_someApiCall_today)
# or
# src.someSloowApiCall2 = amend_someApiCall_today
elif time == "later":
mocker.patch("_main.src.someSloowApiCall2", amend_someApiCall_later2)
# or
# src.someSloowApiCall2 = amend_someApiCall_later
elif time == "tomorrow":
mocker.patch("_main.src.someSloowApiCall2", lambda var : f'lambda2({var})')
# or
# src.someSloowApiCall2 = lambda var: 0
else:
pass
# or
# src.someSloowApiCall2 = someSloowApiCall2
return original_func(time, var)
return amend_someFunction2
#pytest.mark.parametrize(
'time',
[
'yesterday',
'today',
'later',
'tomorrow',
'whenever',
],
)
def test_entrypointFunction(time, mocker):
mocker.patch.object(
_main.src.SomeClass,
"someFunction",
side_effect=get_amended_someFunction(mocker, _main.src.SomeClass.someFunction),
autospec=True, # Needed for the self argument
)
# or
# src.SomeClass.someFunction = get_amended_someFunction(mocker, src.SomeClass.someFunction)
mocker.patch(
"_main.src.someFunction2",
side_effect=get_amended_someFunction2(mocker, _main.src.someFunction2),
)
# or
# src.someFunction2 = get_amended_someFunction2(mocker, src.someFunction2)
uut = _main.MyMainClass()
print(f'\nuut.entry_point({time})-->{uut.entry_point(time)}')
Output:
$ pytest -rP
=================================== PASSES ====================================
_____________________ test_entrypointFunction[yesterday] ______________________
---------------------------- Captured stdout call -----------------------------
uut.entry_point(yesterday)-->
[1]entry_point(yesterday)-->someFunction(yesterday,1)-->amend_someApiCall_yesterday(1)
[2]entry_point(yesterday)-->someFunction2(yesterday,2)-->amend_someApiCall_yesterday(2)
_______________________ test_entrypointFunction[today] ________________________
---------------------------- Captured stdout call -----------------------------
uut.entry_point(today)-->
[1]entry_point(today)-->someFunction(today,1)-->amend_someApiCall_today(1)
[2]entry_point(today)-->someFunction2(today,2)-->amend_someApiCall_today(2)
_______________________ test_entrypointFunction[later] ________________________
---------------------------- Captured stdout call -----------------------------
uut.entry_point(later)-->
[1]entry_point(later)-->someFunction(later,1)-->amend_someApiCall_later(1)-->someSloowApiCall(11)-->I'm mocked! I'm not slow, random or hard to test
[2]entry_point(later)-->someFunction2(later,2)-->amend_someApiCall_later2(2)-->someSloowApiCall2(12)-->I'm mocked! I'm not slow, random or hard to test
______________________ test_entrypointFunction[tomorrow] ______________________
---------------------------- Captured stdout call -----------------------------
uut.entry_point(tomorrow)-->
[1]entry_point(tomorrow)-->someFunction(tomorrow,1)-->lambda(1)
[2]entry_point(tomorrow)-->someFunction2(tomorrow,2)-->lambda2(2)
______________________ test_entrypointFunction[whenever] ______________________
---------------------------- Captured stdout call -----------------------------
uut.entry_point(whenever)-->
[1]entry_point(whenever)-->someFunction(whenever,1)-->someSloowApiCall(1)-->I'm not mocked! I'm slow, random and hard to test
[2]entry_point(whenever)-->someFunction2(whenever,2)-->someSloowApiCall2(2)-->I'm not mocked! I'm slow, random and hard to test
============================== 5 passed in 0.07s ==============================
I have the following function in the file myfile.py:
#myfile.py
import psutil
class RunnableObject:
def run(self):
parent = psutil.Process()
print(parent)
children = parent.children(recursive=True)
print(children)
Then I have a unit test where runnable_object is an instance of the RunnableObject class which I setup using a pytest fixture.
#patch("myfile.psutil")
def test_run_post_request(self, psutil_, runnable_object):
runnable_object.run()
assert psutil_.Process.call_count == 1
assert psutil_.Process.children.call_count == 1
When I run my test however I get the following error:
assert psutil_.Process.call_count == 1
> assert psutil_.Process.children.call_count == 1
E assert 0 == 1
E +0
E -1
-1
tests/unit/test_experiment.py:1651: AssertionError
My stdout:
<MagicMock name='psutil.Process()' id='3001903696'>
<MagicMock name='psutil.Process().children()' id='3000968624'>
I also tried to use #patch.object(psutil.Process, "children") as well as#patch("myfile.psutil.Process") and #patch("myfile.psutil.Process.children") but that gave me the same problem.
children is the property of the return value of psutil.Process(). NOT the property of the Process method.
So the correct assertion is:
test_myfile.py:
from unittest import TestCase
import unittest
from unittest.mock import patch
from myfile import RunnableObject
class TestRunnableObject(TestCase):
#patch("myfile.psutil")
def test_run_post_request(self, psutil_):
runnable_object = RunnableObject()
runnable_object.run()
assert psutil_.Process.call_count == 1
assert psutil_.Process().children.call_count == 1
if __name__ == '__main__':
unittest.main()
test result:
<MagicMock name='psutil.Process()' id='4394128192'>
<MagicMock name='psutil.Process().children()' id='4394180912'>
.
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
Name Stmts Miss Cover Missing
-------------------------------------------------------------------------
src/stackoverflow/67362647/myfile.py 7 0 100%
src/stackoverflow/67362647/test_myfile.py 13 0 100%
-------------------------------------------------------------------------
TOTAL 20 0 100%
I'm trying to create a pytest plugin that runs a test multiple times to see if it ever passes, skipping on failures unless it's the last run. I use a custom marker for those tests, then pytest_generate_tests to parametrize the test, then modify the result in pytest_pyfunc_call. However, with this approach, the test function needs to actually specify the parameter name, is there some way to hide this parameter from the actual function?
Here's the code for the plugin:
from typing import Optional
import pytest
from _pytest.python import Metafunc, Function
from pluggy.callers import _Result
def pytest_configure(config):
config.addinivalue_line(
"markers", "brute_force: Brute force the test multiple times."
)
class Memory:
def __init__(self, attempts: int):
self.attempts = attempts
self.attempt = 0
self.has_passed = False
def pytest_generate_tests(metafunc: Metafunc):
marker = metafunc.definition.get_closest_marker("brute_force")
if not marker:
return
num_attempts = marker.args and marker.args[0] or 3
memory = Memory(num_attempts)
metafunc.parametrize("memory", [memory for _ in range(num_attempts)])
#pytest.hookimpl(hookwrapper=True)
def pytest_pyfunc_call(pyfuncitem: Function):
memory: Optional[Memory] = pyfuncitem.callspec.params.pop('memory', None)
if memory is not None:
memory.attempt +=1
if memory.has_passed:
raise pytest.skip("already passed")
outcome: _Result = yield
if memory is not None:
if outcome.excinfo is None:
memory.has_passed = True
elif memory.attempt != memory.attempts:
pytest.skip("may pass later")
And here is an example of a test, I want to not have the function to have to specify memory as an argument:
import pytest
num = -1
#pytest.mark.brute_force(3)
def test_simple(memory):
global num
num += 1
assert 10 / num
I am planning to use pytest and pytest-mock for validating the Python code. Being a newbie, wrote a sample code to validate the mock on class and seeing failure. I am wondering what went wrong.
src/main.py
class Main(object):
def __init__(self, my_var=None):
self.var = my_var
def internal_func(self, var=10):
my_var = var + 20
return my_var
def test_func(self):
val = self.internal_func(20)
return val + 40
tests/test_main.py
import pytest
from pytest_mock import mocker
from src.main import Main
def new_func(cls, *args, **kwargs):
return 2
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
val = Main().test_func()
assert Main.internal_func.assert_called_with(20)
It fails with the following error
======================================================================================== FAILURES ========================================================================================
_____________________________________________________________________________________ test_main_mock _____________________________________________________________________________________
mocker = <pytest_mock.MockFixture object at 0x7f34f490d8d0>
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
main = Main()
val = main.test_func()
# assert val == 80
> assert Main.internal_func.assert_called_with(20)
E AssertionError: assert None
E + where None = <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>>(20)
E + where <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>> = <MagicMock name='internal_func' id='139865418160784'>.assert_called_with
E + where <MagicMock name='internal_func' id='139865418160784'> = Main.internal_func
tests/test_main.py:13: AssertionError
The return_value or side_effect must be set before the patched func take effect
def test_main_mock(mocker):
# mock internal_func of class Main
mocked_internal_func = mocker.patch.object(Main, 'internal_func')
# assign return_value or side_effect
mocked_internal_func.return_value = -10
# class instance
ma = Main()
val = ma.test_func()
assert ma.internal_func.assert_called_with(20)
Correction of mistake, the assert should not be used together with assert_called_with, they are independent assert.
assert val == 30
mocked_internal_func.assert_called
ma.internal_func.assert_called_with(20)
mocked_internal_func.assert_called_with(20)
I have a test suite (using nose, not unittest), and I want to patch a function to return a specific sequence of values for every test in the test class. My first attempt, using a simplified example, was:
#patch('time.clock', MagicMock(side_effects=[1, 2]))
class Tests:
def test_1(self):
assert time.clock() == 1
assert time.clock() == 2
def test_2(self):
assert time.clock() == 1
assert time.clock() == 2
However, the MagicMock instance is only created once, so the second test fails when the side effects run out. I can patch each test method separately, but I don't really want to duplicate the patch decorator over all of them (there are a lot more tests than in this example!) The other way I could do it is to create the patch in the setup code like this:
class Tests:
def setup(self):
self.old_clock = time.clock
time.clock = MagicMock(side_effects=[1, 2])
def teardown(self):
time.clock = self.old_clock
def test_1(self):
assert time.clock() == 1
assert time.clock() == 2
def test_2(self):
assert time.clock() == 1
assert time.clock() == 2
But saving and restoring the original function definition seems like something that Mock should be able to do automatically. Is there another method of doing this that I'm missing? Or is my last example the best way of doing this?
a = (x for x in [1,2])
x = lambda : next(a)
x()
Out: 1
x()
Out: 2
Put your answers into a's list.
Change X for your desired name.
You should just apply the patch to every test, instead of applying it to the class:
class Tests:
#patch('time.clock', MagicMock(side_effects=[1, 2]))
def test_1(self):
assert time.clock() == 1
assert time.clock() == 2
#patch('time.clock', MagicMock(side_effects=[1, 2]))
def test_2(self):
assert time.clock() == 1
assert time.clock() == 2