How to mock function call inside other function using pytest? - python

def publish_book(publisher):
# some calculations
factor = 4
print('xyz')
db_entry(factor) # db entry call which I want to mock
print('abc')
def update():
publish_book('xyz')
#pytest.mark.django_db
def test_update(mocker):
# in here I'm unable to mock nested function call
pass
I have db_entry() function call in publish_book(). how can we mock db_entry() function call inside publish_book.I want to perform other calculations of publish_book(), but only skip(mock) the db_entry() call.

You can use monkeypatch to mock functions. Here is an example if it helps you.
def db_entry():
return True
def add_num(x, y):
return x + y
def get_status(x, y):
if add_num(x, y) > 5 and db_entry() is True:
return True
else:
return False
def test_get_stats(monkeypatch):
assert get_status(3, 3)
monkeypatch.setattr("pytest_fun.db_entry", lambda: False)
assert not get_status(3, 3)
As you can see before doing the second assertion i am mocking the value of db_entry function to return false. You can use monkeypatch to mock the function to return nothing if you want by using lambda like lambda: None
I am not sure what your db_entry function does but say it is doing some db query and returning list of results you can mock that also using lambda by returning lambda: ["foobar"]

Related

Is there a simple way to trace function calls in a complex call without mocking everything separately?

Assume that I don't care about occurring exceptions at all. I have a complex function that calls multiple functions along the way.. I want to test that with certain input parameters, certain functions will be called.
So basically, I am looking for something like:
#patch(
"service.module.class.some_nested_function_1",
new_callable=AsyncMock,
)
#patch(
"service.module.class.some_nested_function_2",
new_callable=AsyncMock,
)
#pytest.mark.asyncio
async def test_complex_function(function2_mock, function1_mock):
some_param = "abc"
another_param = "xyz"
try:
call_complex_function("with_some_configuration")
except:
abort_crashing_function_and_continue_execution_in_complex_function()
assert function2_mock.assert_called_once_with(some_param)
assert function1_mock.assert_called_once_with(another_param)
EDIT: An alternative idea would be to have something like:
...
async def test_complex_function(function2_mock, function1_mock):
...
mock_every_function_call_except_complex_function_to_return_zero()
call_complex_function("with_some_configuration")
assert function2_mock.assert_called_once_with(some_param)
assert function1_mock.assert_called_once_with(another_param)
...
...
Function Call Counting Wrapper
Wrap your nested functions with this so that mocks aren't necessary and you can still see if certain functions were called or not called.
def count_calls_wrapper(fn):
call_count = 0
def _result(*args):
nonlocal call_count
call_count +=1
return fn(*args)
def _get_call_count():
return call_count
_result.get_call_count = _get_call_count
return _result
def f1():
return 10
f1 = count_calls_wrapper(f1)
assert(f1.get_call_count() == 0)
f1()
assert(f1.get_call_count() == 1)

Explain how probSecond.calls equal to zero

def MainCount(f):
def progFirst(*args,**kwargs):
progFirst.calls+=1
return f(*args,**kwargs)
progFirst.calls=0
return progFirst
#MainCount
def progSecond(i):
return i+1
#MainCount
def Count(i=0,j=1):
return i*j+1
print(progSecond.calls)
for n in range(5):
progSecond(n)
Count(j=0,i=1)
print(Count.calls)
Output :0
1
As per my understanding MainCount(probSecond) but I am not understant then how probSecond.calls equal to zero same in Count.calls also
As You Can See in MainCount function probFirst.Calls is attribute of function .When MainCount(probSecond) Now probSecond.calls is also attribute of MainCount function.
# A Python example to demonstrate that
# decorators can be useful attach data
# A decorator function to attach
# data to func
def attach_data(func):
func.data = 3
return func
#attach_data
def add (x, y):
return x + y
# Driver code
# This call is equivalent to attach_data()
# with add() as parameter
print(add(2, 3))
print(add.data)

Can python decorators be used to change behavior of a function returned by a function?

I have functions, that return validator functions, simple example:
def check_len(n):
return lambda s: len(s) == n
Is it possible to add a decorator, that prints out a message, in case the check evaluates to false?
Something like this:
#log_false_but_how
def check_len(n):
return lambda s: len(s) == n
check_one = check_len(1)
print(check_one('a')) # returns True
print(check_one('abc')) # return False
Expected output:
True
validator evaluated to False
False
I've tried creating an annotation, but can only access the function creation with it.
One way would be to define the functions like this:
def log_false(fn):
def inner(*args):
res = fn(*args)
if not res:
print("validation failed for {}".format(fn.__name__))
return res
return inner
#log_false
def check_one(s):
return check_len(1)(s)
But this way we lose the dynamic creation of validation functions.
You're doing the validation in the wrong place. check_len is a function factory, so res is not a boolean - it's a function. Your #log_false decorator has to wrap a validator function around each lambda returned by check_len. Basically you need to write a decorator that decorates the returned functions.
def log_false(validator_factory):
# We'll create a wrapper for the validator_factory
# that applies a decorator to each function returned
# by the factory
def check_result(validator):
#functools.wraps(validator)
def wrapper(*args, **kwargs):
result = validator(*args, **kwargs)
if not result:
name = validator_factory.__name__
print('validation failed for {}'.format(name))
return result
return wrapper
#functools.wraps(validator_factory)
def wrapper(*args, **kwargs):
validator = validator_factory(*args, **kwargs)
return check_result(validator)
return wrapper
Result:
#log_false
def check_len(n):
return lambda s: len(s) == n
check_one = check_len(1)
print(check_one('a')) # prints nothing
print(check_one('abc')) # prints "validation failed for check_len"

Mock function to return different value based on argument passed

In my test function, my mocks are not behaving properly because when I mock my get_config function, I only know how to mock it to return one value. How can I add some logic to my mock in order for the mock to return a different value only when get_config is passed the argument of "restricted"
def func():
conf1 = get_config("conf1")
conf2 = get_config("conf2")
conf3 = get_config("conf3")
restricted_datasets = get_config("restricted")
dataset = get_config("dataset")
if dataset not in restricted_datas:
return run_code()
You can assign a function to side_effect as described in official doc
from unittest.mock import Mock
def side_effect(value):
return value
m = Mock(side_effect=side_effect)
m('restricted')
'restricted'
class Ctx():
def get_config(confName):
return confName
def mock_get_config(value):
if value == "conf1":
return "confA"
elif value == "conf2":
return "confB"
else:
return "UnknownValue"
class CtxSourceFileTrial(unittest.TestCase):
def test(self):
mockCtx = Mock()
mockCtx.get_config.side_effect = mock_get_config
self.assertEqual("confA", mockCtx.get_config("conf1"))
self.assertEqual("confB", mockCtx.get_config("conf2"))
#
# By the way I think Python is EXTREMELY screwy, Adligo
#

How can I write a simple callback function?

I have this example code, trying to demonstrate using a callback function:
def callback(a, b):
print('Sum = {0}'.format(a+b))
def main(callback=None):
print('Add any two digits.')
if callback != None:
callback
main(callback(1, 2))
I get this result:
Sum = 3
Add any two digits.
It seems that the callback function executes before the logic in main. Why? How can I make it so that the callback is not called until it is used within main?
See also: Python Argument Binders
In this code
if callback != None:
callback
callback on its own doesn't do anything; it accepts parameters - def callback(a, b):
The fact that you did callback(1, 2) first will call that function, thereby printing Sum = 3, and then main() gets called with the result of the callback function, which is printing the second line
Since callback returns no explicit value, it is returned as None.
Thus, your code is equivalent to
callback(1, 2)
main()
Solution
You could try not calling the function at first and just passing its handle.
def callback(n):
print("Sum = {}".format(n))
def main(a, b, _callback = None):
print("adding {} + {}".format(a, b))
if _callback:
_callback(a+b)
main(1, 2, callback)
Here's what you wanted to do :
def callback(a, b):
print('Sum = {0}'.format(a+b))
def main(a,b,f=None):
print('Add any two digits.')
if f is not None:
f(a,b)
main(1, 2, callback)
The problem is that you're evaluating the callback before you pass it as a callable. One flexible way to solve the problem would be this:
def callback1(a, b):
print('Sum = {0}'.format(a+b))
def callback2(a):
print('Square = {0}'.format(a**2))
def callback3():
print('Hello, world!')
def main(callback=None, cargs=()):
print('Calling callback.')
if callback is not None:
callback(*cargs)
main(callback1, cargs=(1, 2))
main(callback2, cargs=(2,))
main(callback3)
Optionally you may want to include a way to support keyword arguments.
As mentioned in the comments, your callback is called whenever it's suffixed with open and close parens; thus it's called when you pass it.
You might want to use a lambda and pass in the values.
#!/usr/bin/env python3
def main(callback=None, x=None, y=None):
print('Add any two digits.')
if callback != None and x != None and y != None:
print("Result of callback is {0}".format(callback(x,y)))
else:
print("Missing values...")
if __name__ == "__main__":
main(lambda x, y: x+y, 1, 2)
Your code is executed as follows:
main(callback(1, 2))
callback function is called with (1, 2) and it returns None (Without return statement, your function prints Sum = 3 and returns None)
main function is called with None as argument (So callback != None will always be False)
This is an old post, but perhaps the following may be additional clarification on writing and using a callback function, especially if you wonder where it gets its arguments from and whether you can access its return values (if there is no way to get it from the function that takes the callback function).
The following code defines a class CallBack that has two callback methods (functions) my_callback_sum and my_callback_multiply. The callback methods are fed into the method foo.
# understanding callback
class CallBack:
#classmethod
def my_callback_sum(cls, c_value1, c_value2):
value = c_value1 + c_value2
print(f'in my_callback_sum --> {c_value1} + {c_value2} = {value}')
cls.operator = '+'
return cls.operator, value
#classmethod
def my_callback_multiply(cls, c_value1, c_value2):
value = c_value1 * c_value2
print(f'in my_callback_multiply --> {c_value1} * {c_value2} = {value}')
cls.operator = '*'
return cls.operator, value
#staticmethod
def foo(foo_value, callback):
_, value = callback(10, foo_value)
# note foo only returns the value not the operator from callback!
return value
if __name__ == '__main__':
cb = CallBack()
value = cb.foo(20, cb.my_callback_sum)
print(f'in main --> {value} and the operator is {cb.operator}')
value = cb.foo(20, cb.my_callback_multiply)
print(f'in main --> {value} and the operator is {cb.operator}')
result:
in my_callback_sum --> 10 + 20 = 30
in main --> 30 and the operator is +
in my_callback_multiply --> 10 * 20 = 200
in main --> 200 and the operator is *
As you can see one value for the callback function c_value2 it gets from argument foo_value in foo and given in main the value 20, while c_value1 it gets internally from foo in this case the value 10 (and may be not clearly visible if foo is some method of a third party imported module, like pyaudio).
The return value of the callback function functions can be retrieved by adding it to the namespace of the class CallBack, in this case cls.operator
You can use anonymous functions
def callback(a, b):
print('Sum = {0}'.format(a+b))
def main(callback=None):
print('Add any two digits.')
if callback is not None:
callback()
tmp_func = lambda: main(lambda: callback(2,3))
tmp_func()
#OR
tmp_func = lambda x,y: main(lambda: callback(x,y))
tmp_func(2,4)

Categories