Mocking An Api Call Within A Function Based On Inputs
Solution 1:
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):
returnvar + 2./test_src.py
from importlib import reload
import sys
import api
from api import someApiCall
from src import someFunction
import pytest
defamend_someApiCall_yesterday(var):
# Reimplement api.someApiCallreturn var * 2defamend_someApiCall_now(var):
# Reimplement api.someApiCallreturn var * 3defamend_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 *= 4return someApiCall(var)
defsomeFunction_decorator_patch(someFunction, mocker):
defwrapper(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.someApiCallpassreturn someFunction(time, a, b, c)
return wrapper
defsomeFunction_decorator_reload(someFunction):
defwrapper(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: 0else:
# 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',
],
)deftest_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.
yesterdaymeans var * 2nowmeans var * 3latermeans (var * 4) + 2tomorrowmeans 0- anything else means the default implementation of var + 2
Solution 2:
Let's say that your test file is test.py, and your library file is lib.py. Then test.py should read as follows:
import lib
deffakeApiCall(*args, **kwargs):
return"fake"
lib.someApiCall = fakeApiCall
lib.someFunction(args)
The someApiCall method is just a variable in the namespace of the module in question. So, change the value of that variable. You may need to dig into locals() and/or globals(), like so:
data = Nonelocals()['data'] = "data"print(data) # will print "data"
Post a Comment for "Mocking An Api Call Within A Function Based On Inputs"