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.
yesterday
means var * 2now
means var * 3later
means (var * 4) + 2tomorrow
means 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"