Mocking sqlite3-using function - python

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!

Related

Mock object with pyTest

I have a function I need to test:
def get_user_by_username(db, username: str) -> Waiter:
"""Get user object based on given username."""
user = db.query(Waiter).filter(Waiter.username == username).first()
return user
Here I try to mock DB call and return correct Waiter object, so my test is:
def test_get_user_by_username(mocker):
waiter = Waiter(id=10, username="string", password="1111")
db_mock = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert result.json()["username"] == "string"
But I get an error TypeError: 'Mock' object is not subscriptable. How I can do it?
According to #Don Kirkby answer, the right solution is:
def test_get_user_by_username(mocker):
waiter = Waiter(id=10, username="string", password="1111")
filter_method = mocker.Mock()
filter_method.first = mocker.Mock(return_value=waiter)
query = mocker.Mock()
query.filter = mocker.Mock(return_value=filter_method)
db_mock = mocker.Mock()
db_mock.query = mocker.Mock(return_value=query)
db_mock.query.filter.first = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert vars(result)["username"] == "string"
You're calling several methods, so you need to mock all of them:
query()
filter()
first()
Here's my best guess at how to do that with your code:
def test_get_user_by_username():
waiter = Waiter(id=10, username="string", password="1111")
filter_method = mocker.Mock()
filter_method.first = mocker.Mock(return_value=waiter)
query = mocker.Mock()
query.filter = mocker.Mock(return_value=filter_method)
db_mock = mocker.Mock()
db_mock.query = mocker.Mock(return_value=query)
db_mock.query.filter.first = mocker.Mock(return_value=waiter)
result = get_user_by_username(db=db_mock, username="string")
assert result.json()["username"] == "string"
I'm not sure what database library you're using, but I recommend looking for a library that provides mock versions of the database methods. For example, I contributed a few features to django-mock-queries that lets you mock out the Django ORM. Either that, or separate the logic you want to test from the database access code.

Issue Mocking Multiple Items on Path Object

I have code that looks like the following:
#patch.object(Path, "__init__", return_value=None)
#patch.object(Path, "exists", return_value=True)
#patch.object(Path, "read_text", return_value="FAKEFIELD: 'FAKEVALUE'")
def test_load_param_from_file_exists(self, *mock_path):
expected_dict = YAML.safe_load("FAKEFIELD: 'FAKEVALUE'")
return_dict = load_parameters("input", None)
self.assertTrue(return_dict["FAKEFIELD"] == expected_dict["FAKEFIELD"])
and deep in the code of load_parameters, the code looks like this:
file_path = Path(parameters_file_path)
if file_path.exists():
file_contents = file_path.read_text()
return YAML.safe_load(file_contents)
Right now, I have to break it up into two tests, because I cannot seem to get a single mock object that allows me to switch between "file exists" and "file doesn't". Ideally, I'd be able to do a single test like this:
#patch.object(Path, "__init__", return_value=None)
#patch.object(Path, "exists", return_value=False)
#patch.object(Path, "read_text", return_value="FAKEFIELD: 'FAKEVALUE'")
def test_load_param_from_file(self, mock_path, *mock_path_other):
with self.assertRaises(ValueError):
load_parameters("input", False)
mock_path.read_text.return_value = "FAKEFIELD: 'FAKEVALUE'"
expected_dict = YAML.safe_load("FAKEFIELD: 'FAKEVALUE'")
return_dict = load_parameters("input", None)
self.assertTrue(return_dict["FAKEFIELD"] == expected_dict["FAKEFIELD"])
To be clear, the above doesn't work because each of those patched objects get instantiated differently, and when the Path object in the load_parameters method gets called, exists is mocked correctly, but read_text returns no value.
What am I doing wrong? Is there a way to patch multiple methods on a single object or class?
I think you are making this more complicated than it needs to be:
def test_load_param_from_file_exists(self):
# Adjust the name as necessary
mock_path = Mock()
mock_path.exists.return_value = True
mock_path.read_text.return_value = '{"FAKEFIELD": "FAKEVALUE"}'
with patch("Path", return_value=mock_path):
return_dict = load_parameters("input", None)
self.assertTrue(return_dict["FAKEFIELD"] == 'FAKEVALUE')
Configure a Mock to behave like you want file_path to behave, then patch Path to return that object when it is called.
(I removed the code involving the environment variable, since it wasn't obvious the value matters when you patch Path.)

#patch mock to mysqldb connection is not working

I am writing Unit test for Python app that connects to Mysql using MySQLdb .
There is a function that connects to Mysql db and returns the connection object.
def connect_to_database():
conn = MySQLdb.connect(host=db_pb2['mysql_host'],
user=db_pb2['mysql_user'],
passwd=db_pb2['mysql_password'],
db=db_pb2['mysql_db'])
return conn
There is one more function that executes the query using the above connection
def execute_query():
cur = connect_to_database().cursor()
a = cur.execute("query")
if a > 0:
result = cur.fetchall()
return result
I have written #patch to mock the return value from the cur.fetchall() and cur.execute() methods
#patch('application.module1.data_adapters.connect_to_database')
def test_daily_test_failures(self, db_connection):
db_connection.cursor().execute.return_value = 1
db_connection.cursor().fetchall. \
return_value = ((1,5,6),)
self.assertEqual((execute_query(),
((1,5,6),))
I get the following error:
if a > 0:
TypeError: '<=' not supported between instances of 'MagicMock' and 'int'
Seems like the return value in patch function is not working as expected
Your patching requires a bit more effort.
#patch('application.module1.data_adapters.connect_to_database')
def test_daily_test_failures(self, connect_to_database):
db_connection = MagicMock()
connect_to_database.return_value = db_connection # 1.
mock_cursor = MagicMock()
db_connection.cursor.return_value = mock_cursor # 2.
mock_cursor.fetchall.return_value = ((1,5,6),)
self.assertEqual((execute_query(),
((1,5,6),))
You're patching the symbol connect_to_database - that does not mock the call to the function. You need to specify that "when that symbol is called, do this"
When mocking function calls, you can't do it like this: db_connection.cursor(). - you need to make db_connection.cursor return a mock, & then specify the .return_value for that mock object

How can I test the amazon api?

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 = ...

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.

Categories