python unittest mock a nested function - python

i have a function x in main.applications.handlers package
from main.config import get_db
def x(company_name):
db = get_db('my_db')
apps = []
for x in company_db.applications.find():
print(x)
apps.append(x)
return apps
now i want to write unittest for this method .
from unittest.mock import Mock,patch, MagicMock
#mock.patch('main.applications.handlers.get_db')
def test_show_applications_handler(self, mocked_db):
mocked_db.applications.find = MagicMock(return_value=[1,2,3])
apps = x('test_company') # apps should have [1,2,3] but its []
print(apps)
but company_db.applications.find() inside main.applications.handlers is not returning anything .it should return [1,2,3]
what could be wrong with this code?

Assuming that company_db is a typo and should be db, then to mock the return value of find(), you would do:
mocked_db.return_value.applications.find = MagicMock(return_value=[1,2,3])
mocked_db requires a return_value because get_db is called with the database name.
You could also drop the MagicMock and set the return_value of find directly:
mocked_db.return_value.applications.find.return_value = [1, 2, 3]

Related

How to write pytest for boto3 lambda invoke when it is defined inside a function

I am trying to write pytest to test the following method by mocking the boto3 client. I tried with sample test case. I am not sure if that is right way to do it. Please correct me if I am wrong.
//temp.py
import boto3
import json
def temp_lambda(event):
client_lam = boto3.client('lambda', region_name="eu-west-1") #defined inside the function.
obj = client_lam.invoke(
FunctionName='XYZ',
InvocationType='ABC',
Payload=json.dumps({'payload': event}))
return obj
//test_temp.py
import mock
from unittest.mock import MagicMock, patch
from .temp import temp_lambda
#mock.patch("boto3.client")
def test_temp_lambda(mock_lambda_client):
mocked_response = MagicMock(return_value = 'yes')
mock_lambda_client.invoke.return_value = mocked_response.return_value
event = {}
x = temp_lambda(event)
assert x == 'yes'
I am getting assertion error in output
AssertionError: assert <MagicMock name='client().invoke()' id='2557742644480'> == 'yes'
def test_temp_lambda(context):
with patch('boto3.client') as mocked_response:
mocked= MagicMock()
mocked.invoke.return_value= "ok"
mocked_response.return_value=mocked
event = {}
x = temp_lambda(event)
assert response=='ok'
The golden rule of the mock framework is that you mock where the object is used not where it's defined.
So it would be #mock.patch('temp.boto3.client')

How to mock uuid4() in python?

A_script.py
from uuid import uuid4
def get_unique_identifier(env, customer_id):
return env + '-' + customer_id + '-' + str(uuid4())[0:8]
test_A_script.py
import unittest
from unittest.mock import patch
import src.A_script as a_script
class MyTestCase(unittest.TestCase):
#patch('uuid.uuid4')
def test_get_unique_identifier(self, mock_uuid4):
mock_uuid4.return_value = 'abcd1234'
expected = 'test_env-test_cust-abcd1234'
unique_identifier = a_script.get_unique_identifier('test_env', 'test_cust')
self.assertEqual(expected, unique_identifier)
How can I make uuid4 return 'abcd1234'?
You have to patch uuid that are imported in your srcipt, so change
#patch('uuid.uuid4')
to
#patch('src.A_script.uuid4')
# or #patch('src.A_script.uuid4', return_value="abcd1234")
# or #patch('src.A_script.uuid4', new=lambda:"abcd1234")
In last case you do not pass mock as function parameter at all
You need to mock the method in the module where it's used. In your case you use it in module A_script so you have to call patch with 'A_script.uuid4.

How to mock modules python, patch does not find the attribute

In a function I'm using uuid1 that I want to patch.
def myFunction():
my_value = uuid4.int
smth else..
I want to be able to mock my_value so it always returns the same number in my unit test, because I need it for further use.
I tried doing:
#patch('folder.myFunction.uuid4')
def test_myFunction(self, mock_value):
mock_value.return_value = 22222
But it throws an error saying myFunction does not have uuid4 as an attribute.
How do I mock its value?
The error you get is correct. Your function does not have a uuid4 attribute.
I'm reading between the lines assuming uuid4 is a method of the uuid module that normally generates a random uuid.
When testing you said you want it to always return the same value. To do that you can substitute a unittest.mock.Mock for uuid.uuid4.
In [36]: uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e'))
In [37]: uuid_mock()
Out[37]: UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e')
Something like this for testing the following function (f)
import uuid, unittest
from unittest.mock import Mock, patch
def f():
z = uuid.uuid4()
return z.int
The target for the patch is the uuid method - uuid.uuid4. Specify a unittest.mock.Mock with a fixed return value for the new parameter of the patch. During the test, the Mock will be substituted for uuid.uuid4
class TestF(unittest.TestCase):
uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e'))
good_uuid = uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e').int
bad_uuid = uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b5a31').int
#patch(target='uuid.uuid4', new=TestF.uuid_mock)
def test_myFunction_True(self):
self.assertEqual(f(), self.good_uuid)
#patch(target='uuid.uuid4', new=TestF.uuid_mock)
def test_myFunction_False(self):
self.assertNotEqual(f(), self.bad_uuid)
if __name__ == '__main__':
unittest.main()
Result:
..
----------------------------------------------------------------------
Ran 2 tests in 0.001s
OK
If you want to test a function that relies on f's return value and you want f to always return the same value during testing then make f the target for the patch.
def g():
return f() == uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e').int
class TestG(unittest.TestCase):
good_uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b9f3e').int)
bad_uuid_mock = Mock(return_value=uuid.UUID('77f1df52-4b43-11e9-910f-b8ca3a9b5a31').int)
#patch(target='__main__.f', new=TestG.good_uuid_mock)
def test_myFunction_True(self):
self.assertTrue(g())
#patch(target='__main__.f', new=TestG.bad_uuid_mock)
def test_myFunction_False(self):
self.assertFalse(g())
It depends on your import. Let's say you have a module called module.py, and you have an import like this:
from uuid import uuid4
This means that in this module we now have a variable called uuid4. This is the thing to mock.
#patch('path.to.module.uuid4.int')

Trouble patching a function of a function

I'm pretty new to patching and I've run into a something I don't know how to patch. Basically, in the file I want to test, there is the method difficult_method(). It looks a little like this:
from import_location import User
def difficult_method():
ids = list_of_ids
for id in list_of_ids:
try:
user = User.query.filter(User.id == user_id).all()[0]
except:
continue
#do lots of stuff
The code I want to mock is User.query.filter(User.id == user_id).all() and as far as I am concerned it can return a static list. How would I replace that line in code that looks something like this:
from mock import patch
#patch(#what would go here?)
def test_difficult_method():
from file_to_test import difficult_method
assert difficult_method() returns ...
I figured it out! The key was to create a MockUser class, like so:
user = #creating a user
class MockFilter(object):
def all(self):
return [user]
class MockQuery(object):
def filter(self, match):
return MockFilter()
class MockUser(object):
query = MockQuery()
id = '2'
Then I patched it in like so:
from mock import patch
#patch('import_location.User', MockUser)
def test_difficult_method():
from file_to_test import difficult_method
assert difficult_method() returns ...

mock/patch os.path.exists with multiple return values

I'm trying to test a function that I made that iterates through a list, and calls os.path.exists for each item in the list. My test is passing the function a list of 2 objects. I need os.path.exists to return True for one of them and False for the other. I have tried this:
import mock
import os
import unittest
class TestClass(unittest.TestCase):
values = {1 : True, 2 : False}
def side_effect(arg):
return values[arg]
def testFunction(self):
with mock.patch('os.path.exists') as m:
m.return_value = side_effect # 1
m.side_effect = side_effect # 2
arglist = [1, 2]
ret = test(argList)
Using either but not both of line #1 and #2 give NameError: global name 'side_effect' is not defined
I found this question and modified my code like so:
import mock
import os
class TestClass(unittest.TestCase):
values = {1 : True, 2 : False}
def side_effect(arg):
return values[arg]
def testFunction(self):
mockobj = mock(spec=os.path.exists)
mockobj.side_effect = side_effect
arglist = [1, 2]
ret = test(argList)
And this produces TypeError: 'module' object is not callable.
I also tried switching these lines:
mockobj = mock(spec=os.path.exists)
mockobj.side_effect = side_effect
for this
mockobj = mock(spec=os.path)
mockobj.exists.side_effect = side_effect
and this
mockobj = mock(spec=os)
mockobj.path.exists.side_effect = side_effect
with the same error being produced. Can anyone point out what it is that I am doing wrong and what I can do to get this to work?
EDIT:
After posting my answer below I realised that my first bit of code actually works as well, I just needed m.side_effect = TestClass.side_effect instead of m.side_effect = side_effect.
So after a bit more research and trial and error, with most of the examples here: http://www.voidspace.org.uk/python/mock/patch.html, I solved my problem.
import mock
import os
def side_effect(arg):
if arg == 1:
return True
else:
return False
class TestClass(unittest.TestCase):
patcher = mock.patch('os.path.exists')
mock_thing = patcher.start()
mock_thing.side_effect = side_effect
arg_list = [1, 2]
ret = test(arg_list)
self.assertItemsEqual([1], ret)
test calls os.path.exist for each item in arg_list, and returns a list of all items that os.path.exist returned True for. This test now passes how I want it.
you could have done self.side_effect I believe. since the initial definition was not global, calling side_effect looks inside the global scope

Categories