How can I test the amazon api? - python

I have the following code.
How can I test the function create_items_by_parent_asin?
def get_amazon():
return AmazonAPI(settings.AMAZON_ACCESS_KEY, settings.AMAZON_SECRET_KEY, settings.AMAZON_ASSOC_TAG)
def get_item_by_asin(asin: str, response_group='Large'):
amazon = get_amazon()
product = amazon.lookup(ItemId=asin, ResponseGroup=response_group)
return product
def create_items_by_parent_asin(self, asin: str):
amazon_item = get_item_by_asin(asin, response_group='Large')
....

You don't test the API, you mock the interactions with amazon away with a different implementation of AmazonAPI.
In python this can be done using unittest.mock: https://docs.python.org/3/library/unittest.mock.html
It's been a long time since I've done this in python, but iirc you can just do something like this in your testclasses (untested, I adapted the example from the docs):
testproduct = ... # static product you will use in your tests
with patch('AmazonAPI') as mock:
instance = mock.return_value
instance.lookup.return_value = testproduct
product = x.create_items_by_parent_asin("...") # this should now be your testproduct
If product is a non-trivial thing to create an instance of you can also mock this away by doing:
testproduct = Mock()
testproduct.<method you want to mock>.return_value = ...

Related

Not sure why MyMock.env["key1"].search.side_effect=["a", "b"] works but MyMock.env["key1"] = ["a"] with MyMock.env["key2"] = ["b"] does not work

I had created a simple example to illustrate my issue. First is the setup say mydummy.py:
class TstObj:
def __init__(self, name):
self.name = name
def search(self):
return self.name
MyData = {}
MyData["object1"] = TstObj("object1")
MyData["object2"] = TstObj("object2")
MyData["object3"] = TstObj("object3")
def getObject1Data():
return MyData["object1"].search()
def getObject2Data():
return MyData["object2"].search()
def getObject3Data():
return MyData["object3"].search()
def getExample():
res = f"{getObject1Data()}{getObject2Data()}{getObject3Data()}"
return res
Here is the test that failed.
def test_get_dummy1():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1"]
mydummy.MyData["object2"].search.side_effect = ["obj2"]
mydummy.MyData["object3"].search.side_effect = ["obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
The above failed with run time error:
/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py:1078: StopIteration
Here is the test that passed:
def test_get_dummy2():
dummy.MyData = MagicMock()
mydummy.MyData["object1"].search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Am I missing something? I would have expected test_get_dummy1() to work and test_get_dummy2() to fail and not vice versa. Where and how can I find/learn more information about mocking to explain what is going on...
MyData["object1"] is converted to this function call: MyData.__getitem__("object1"). When you call your getExample method, the __getitem__ method is called 3 times with 3 parameters ("object1", "object2", "object3").
To mock the behavior you could have written your test like so:
def test_get_dummy_alternative():
mydummy.MyData = MagicMock()
mydummy.MyData.__getitem__.return_value.search.side_effect = ["obj1", "obj2", "obj3"]
assert mydummy.getExample() == "obj1obj2obj3"
Note the small change from your version: mydummy.MyData["object1"]... became: mydummy.MyData.__getitem__.return_value.... This is the regular MagicMock syntax - we want to to change the return value of the __getitem__ method.
BONUS:
I often struggle with mock syntax and understanding what's happening under the hood. This is why I wrote a helper library: the pytest-mock-generator. It can show you the actual calls made to the mock object.
To use it in your case you could have added this "exploration test":
def test_get_dummy_explore(mg):
mydummy.MyData = MagicMock()
mydummy.getExample()
mg.generate_asserts(mydummy.MyData, name='mydummy.MyData')
When you execute this test, the following output is printed to the console, which contains all the asserts to the actual calls to the mock:
from mock import call
mydummy.MyData.__getitem__.assert_has_calls(calls=[call('object1'),call('object2'),call('object3'),])
mydummy.MyData.__getitem__.return_value.search.assert_has_calls(calls=[call(),call(),call(),])
mydummy.MyData.__getitem__.return_value.search.return_value.__str__.assert_has_calls(calls=[call(),call(),call(),])
You can easily derive from here what has to be mocked.

Mocking sqlite3-using function

I'm trying to write a unit test to the following function:
import sqlite3
def match_user_id_with_image_uid(image_uid):
# Given the note id, confirm if the current user is the owner of the note which is
# being operated.
_conn = sqlite3.connect(image_db_file_location)
_c = _conn.cursor()
command = "SELECT owner FROM images WHERE uid = '" + image_uid + "';"
_c.execute(command)
result = _c.fetchone()[0]
_conn.commit()
_conn.close()
return result
Pytest's mocker won't allow to mock sqlite3 functions with the following error:
can't set attributes of built-in/extension type 'sqlite3.Cursor'
I'm using mocker like so:
def test_match_user_to_image(mocker):
m = mocker.patch('sqlite3.connect')
m.return_value = mocker.Mock()
Other than that, I wonder that is the best practice to mock out those function? Do I want to patch each and every function (execute, cursor, commit, close).
Thanks!

pytest fixture for mocked object setup?

I'm using pytest and I have 5+ tests that have the exactly same first five lines. Is it possible to create a setup function for the repeated code?
#mock.patch('abcde.xyz')
def test_1(mocked_function):
x_mock = mock.Mock(X)
x_mock.producer = mock.Mock()
x_mock.producer.func2 = lambda : None
mocked_function.return_value = x_mock # xyz() returns x_mock
......
#mock.patch('abcde.xyz')
def test_2(mocked_function):
x_mock = mock.Mock(X)
x_mock.producer = mock.Mock()
x_mock.producer.func2 = lambda : None
mocked_function.return_value = x_mock
......
#mock..... # more
You should consider using a fixture as it recommended over classic setup/teardown methods.
From pytest documentation:
While these setup/teardown methods are simple and familiar to those coming from a unittest or nose background, you may also consider using pytest’s more powerful fixture mechanism which leverages the concept of dependency injection, allowing for a more modular and more scalable approach for managing test state, especially for larger projects and for functional testing.
For your example - considering that mocked_function is itself a fixture - it would be:
#pytest.fixture()
def mock_abcde_xyz(mocker):
mocker.patch("abcde.xyz")
#pytest.fixture()
#pytest.mark.usefixtures("mock_abcde_xyz")
def patched_mocked_function(mocker, mocked_function):
x_mock = mocker.Mock(X)
x_mock.producer = mocker.Mock()
x_mock.producer.func2 = lambda : None
mocked_function.return_value = x_mock
return mocked_function
def test_1(patched_mocked_function):
......
def test_2(patched_mocked_function):
......
Note that I used pytest-mock instead of mock so that you can use 'mocker' fixture.
If you don't want pytest-mock, just do:
#pytest.fixture()
#mock.patch('abcde.xyz')
def patched_mocked_function(mocked_function):
x_mock = mock.Mock(X)
x_mock.producer = mock.Mock()
x_mock.producer.func2 = lambda : None
mocked_function.return_value = x_mock
return mocked_function
def test_1(patched_mocked_function):
......
def test_2(patched_mocked_function):
......
Yes, you can implement a setUp() (or setUpClass()) method.
Another way would be to implement a helper function, as you would in any other Python code.

Pytest Fixtures - Parameterisation - Call Fixture Once

I have a fixture that returns the endpoint for the name of that endpoint (passed in)
The name is a string set in the test. I have messed up by calling the endpoint each time in the tests (parameterised) and now I can't figure out how to get the same functionality working without calling the endpoint each time.
Basically I just need to call the endpoint once and then pass that data between all my tests in that file (Ideally without anything like creating a class and calling it in the test. I have about 12 files each with similar tests and I want to reduce the boiler plate. Ideally if it could be done at the fixture/parametrisation level with no globals.
Here's what I have so far:
#pytest.mark.parametrize('field', [('beskrivelse'), ('systemId')])
def test_intgra_001_elevforhold_req_fields(return_endpoint, field):
ep_to_get = 'get_elevforhold'
ep_returned = return_endpoint(ep_to_get)
apiv2 = Apiv2()
apiv2.entity_check(ep_returned, field, ep_to_get, False)
#pytest.fixture()
def return_endpoint():
def endpoint_initialisation(ep_name):
apiv2 = Apiv2()
ep_data = apiv2.get_ep_name(ep_name)
response = apiv2.get_endpoint_local(ep_data, 200)
content = json.loads(response.content)
apiv2.content_filt(content)
apiv2_data = content['data']
return apiv2_data
return endpoint_initialisation
Create return_endpoint as a fixture with scope session and store data in a dictionary after it is fetched. The fixture doesn't return the initialization function, but a function to access the dictionary.
#pytest.mark.parametrize('field', [('beskrivelse'), ('systemId')])
def test_intgra_001_elevforhold_req_fields(return_endpoint, field):
ep_to_get = 'get_elevforhold'
ep_returned = return_endpoint(ep_to_get)
apiv2 = Apiv2()
apiv2.entity_check(ep_returned, field, ep_to_get, False)
#pytest.fixture(scope='session')
def return_endpoint():
def endpoint_initialisation(ep_name):
apiv2 = Apiv2()
ep_data = apiv2.get_ep_name(ep_name)
response = apiv2.get_endpoint_local(ep_data, 200)
content = json.loads(response.content)
apiv2.content_filt(content)
apiv2_data = content['data']
return apiv2_data
ep_data = dict()
def access(ep_name):
try:
return ep_data[ep_name] # or use copy.deepcopy
except KeyError:
ep_data[ep_name] = endpoint_initialisation(ep_name)
return ep_data[ep_name] # or use copy.deepcopy
return access
There are some caveats here. If the object returned by endpoint_initialisation() is mutable, then you potentially create unwanted dependencies between your tests. You can avoid this by returning a (deep) copy of the object. You can use the copy module for that.

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')

Categories