You mentioned that the behavior of the someApiCall
depends on the time
argument:
... lets say that time is some value I care about a specific output from someApiCall, I would want to make sure the mock returns that...
To do this, then we have to intercept calls to the outer someFunction
and check the time
argument so that we can update the someApiCall
accordingly. One solution is by decorating someFunction
to intercept the calls and modify someApiCall
during runtime based on the time
argument before calling the original someFunction
.
Below is an implementation using decorator. I made 2 possible ways:
- one by patching via
someFunction_decorator_patch
- and another by manually modifying the source code implementation and then performing a reload via
someFunction_decorator_reload
./src.py
from api import someApiCall
def someFunction(time, a, b, c):
apiReturnA = someApiCall(a)
returnB = b + 1
apiReturnC = someApiCall(c)
return [apiReturnA, returnB, apiReturnC]
./api.py
def someApiCall(var):
return var + 2
./test_src.py
from importlib import reload
import sys
import api
from api import someApiCall
from src import someFunction
import pytest
def amend_someApiCall_yesterday(var):
# Reimplement api.someApiCall
return var * 2
def amend_someApiCall_now(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.
var *= 4
return someApiCall(var)
def someFunction_decorator_patch(someFunction, mocker):
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("src.someApiCall", side_effect=amend_someApiCall_yesterday)
elif time == "now":
mocker.patch("src.someApiCall", side_effect=amend_someApiCall_now)
elif time == "later":
mocker.patch("src.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 someFunction_decorator_reload(someFunction):
def wrapper(time, a, b, c):
# If x imports y.z and we want to update the functionality of z, then we have to update
# first the functionality of z then reload x. This way, x would have the updated
# functionality of z.
if time == "yesterday":
api.someApiCall = amend_someApiCall_yesterday
elif time == "now":
api.someApiCall = amend_someApiCall_now
elif time == "later":
api.someApiCall = amend_someApiCall_later
elif time == "tomorrow":
api.someApiCall = lambda var: 0
else:
# Use the original api.someApiCall
api.someApiCall = someApiCall
reload(sys.modules['src'])
return someFunction(time, a, b, c)
return wrapper
@pytest.mark.parametrize(
'time',
[
'yesterday',
'now',
'later',
'tomorrow',
'whenever',
],
)
def test_sample(time, mocker):
a, b, c = 10, 10, 10
someFunction_wrapped_patch = someFunction_decorator_patch(someFunction, mocker)
result_1 = someFunction_wrapped_patch(time, a, b, c)
print("Using patch:", time, result_1)
someFunction_wrapped_reload = someFunction_decorator_reload(someFunction)
result_2 = someFunction_wrapped_reload(time, a, b, c)
print("Using reload:", time, result_2)
Output:
$ pytest -rP
____________________________________________________________________________________ test_sample[yesterday] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: yesterday [20, 11, 20]
Using reload: yesterday [20, 11, 20]
_______________________________________________________________________________________ test_sample[now] ________________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: now [30, 11, 30]
Using reload: now [30, 11, 30]
______________________________________________________________________________________ test_sample[later] _______________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: later [42, 11, 42]
Using reload: later [42, 11, 42]
_____________________________________________________________________________________ test_sample[tomorrow] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: tomorrow [0, 11, 0]
Using reload: tomorrow [0, 11, 0]
_____________________________________________________________________________________ test_sample[whenever] _____________________________________________________________________________________
------------------------------------------------------------------------------------- Captured stdout call --------------------------------------------------------------------------------------
Using patch: whenever [12, 11, 12]
Using reload: whenever [12, 11, 12]
======================================================================================= 5 passed in 0.03s =======================================================================================
Here, you can see that the responses from someApiCall
changes based on the time
argument.
yesterday
means var * 2
now
means var * 3
later
means (var * 4) + 2
tomorrow
means 0
- anything else means the default implementation of var + 2