Unit Test Behavior with Patch (Flask) - python

I am trying to patch methods in my flask api but it appears that the method call is not being replaced. Does app.test_client() do something under the hood that I am missing.
For example if I run
#patch('k.stats.mstats')
def test_ps(self, mstats):
mstats.return_value = (1, 2, 3)
rv = self.app.get('/ps/')
and I run through the debugger to the point below:
#app.route('/ps/', methods=['GET'])
def ps():
import pdb
pdb.set_trace()
mstats()
and inspect mstats, I will get back the function that is unmocked.
However, if I run from k.stats import mstats from the breakpoint, I get back the mocked method that I am looking for.
How do I ensure that the mocked method gets called?

This is a pretty confusing concept, but the documentation of patch tries its best to explain it.
patch works by (temporarily) changing the object that a name points to with another one. There can be many names pointing to any individual object, so for patching to work you must ensure that you patch the name used by the system under test.
The basic principle is that you patch where an object is looked up, which is not necessarily the same place as where it is defined.
This is why you're able to observe the mocked object when you decide to inject it in; you're observing the patched reference where it's looked up at that moment.
The example does an okay job of explaining what's going on there, but I'll try to clarify.
Let's say that mstats lives in module stats. You're importing it through from stats import mstats in module use_stats.
You're going to want to mock it in use_stats, since that's its place of reference.
#patch('use_stats.mstats')
def test_stats(self, mstats):
pass

Related

Patching a method from other file is not working

I have a method say _select_warehouse_for_order in api/controllers/orders.py file. The method is not part of any class.
Now, I have a new file say api/controllers/dispatchers.py where i need to know which warehouse was selected. I am calling _select_warehouse_for_order from this file to get this information.
Now, in my test cases, I am patching _select_warehouse_for_order like this
from unittest.mock import patch, call
def test_delivery_assignment(self, app_context):
with patch('api.controllers.orders._select_warehouse_for_order') as mock_selected_wh:
mock_selected_wh.return_value = {}
app_context.client.set_url_prefix('/v2')
response = app_context.client.get('/delivery/dispatch')
assert response.status_code == 200
The problem that i am facing is that my patch is not returning empty dictionary. when i started debugging, i noticed that its executed the actual code in _select_warehouse_for_order. Am i missing something here?
Update:
Here is the code in dispatchers.py
from api.controllers.orders import _select_warehouse_for_order
#bp.route("/dispatch")
#login_required
def dispatch():
warehouses = _select_warehouse_for_order(request=request)
if len(warehouses) == 0:
logger.info("No warehouse selected")
return
logger.info("Selected warehouse: %s", warehouses[0].name)
# return response
You must patch where the method is used, not where it is declared. In your case, you are patching 'api.controllers.orders._select_warehouse_for_order' which is where the method is declared. Instead, patch 'dispatchers._select_warehouse_for_order' (possibly prefixed with whatever package contains dispatchers).
The reason for this is because when you do
from api.controllers.orders import _select_warehouse_for_order
you declare a name _select_warehouse_for_order in dispatchers.py that refers to the function which is declared in api/controllers/orders.py. Essentially you have created a second reference to the function. Now when you call
warehouses = _select_warehouse_for_order(request=request)
you are using the reference in dispatchers.py, not the one in api/controllers/orders.py. So in order to replace this function with a patch, you have to use dispatchers._select_warehouse_for_order.
Notice how import is different in python than in Java because we create a new name and assign it to an existing function or class. On the other hand, Java imports tell the compiler where to look for a class when it is mentioned in the code.

Python testing: mocking a function that's imported AND used inside another funciton

Due to circular-import issues which are common with Celery tasks in Django, I'm often importing Celery tasks inside of my methods, like so:
# some code omitted for brevity
# accounts/models.py
def refresh_library(self, queue_type="regular"):
from core.tasks import refresh_user_library
refresh_user_library.apply_async(
kwargs={"user_id": self.user.id}, queue=queue_type
)
return 0
In my pytest test for refresh_library, I'd only like to test that refresh_user_library (the Celery task) is called with the correct args and kwargs. But this isn't working:
# tests/test_accounts_models.py
#mock.patch("accounts.models.UserProfile.refresh_library.refresh_user_library")
def test_refresh_library():
Error is about refresh_library not having an attribute refresh_user_library.
I suspect this is due to the fact that the task(refresh_user_library) is imported inside the function itself, but I'm not too experienced with mocking so this might be completely wrong.
Even though apply_async is your own-created function in your core.tasks, if you do not want to test it but only make sure you are giving correct arguments, you need to mock it. In your question you're mocking wrong package. You should do:
# tests/test_accounts_models.py
#mock.patch("core.tasks.rehresh_user_library.apply_sync")
def test_refresh_library():
In your task function, refresh_user_library is a local name, not an attribute of the task. What you want is the real qualified name of the function you want to mock:
#mock.patch("core.tasks.refresh_user_library")
def test_refresh_library():
# you test here

Python unit tests: How to patch an entire class and methods

I am trying to write unittests for existing code which is poorly written and I'm finding it very hard to unit test.
def pay(self):
fraud = NewFraudCheck()
result, transaction = fraud.verify_transaction()
the test I have at the moment, I am patching the NewFraudCheck class
#patch checkout.pay.NewFraudCheck
def test_pay(self, mock_fraud_check):
mock_fraud_check.verify_transaction.assert_called()
The test is failing with a ValueError, stating that verify_transaction is not returning enough values to unpack.
I have tried adding
mock_fraud_check.verify_data.return_value = (1, 1231231)
however this doesn't seemt o have any effect.
There are a few issues I'll point out, but the question is missing a few details so hopefully I can address them all in one shot:
Your syntax here is wrong: #patch checkout.pay.NewFraudCheck. It should be #patch('checkout.pay.NewFraudCheck')
There is a missing class somewhere that has the function pay(self) on it. That class lives inside a module somewhere which is important to properly mock NewFraudCheck. I'll refer to that missing module as other.
NewFraudCheck needs to be patched at the point where it's looked up. That means, in the mystery module other where there's a class that has pay(self) defined in it, there's presumably an import of from pay import NewFraudCheck. That is where NewFraudCheck is looked up, so your patch will need to look like this: #patch('checkout.other.NewFraudCheck). More info here: http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch
You need to assign/use the return value of your patch, not access verify_transaction directly off of the mock. For instance, it should read like this: mock_fraud_check.return_value.verify_transaction.return_value = (1, 1231231). Notice the inclusion of return_value.
The final test I came up with looked like this and passed:
#mock.patch('checkout.other.NewFraudCheck')
def test_pay(self, mock_fraud_check):
# This is the class that lives in mystery module, 'checkout.other' and calls pay()
other_class = SomeOtherClass()
mock_fraud_check.return_value.verify_transaction.return_value = (1, 1231231)
other_class.pay()
mock_fraud_check.return_value.verify_transaction.assert_called()

Two Objects Created from the Same Class, isinstance = False

I'm trying to create some unit tests for some code here at work.
The code takes in an object and based on information within that object imports a specific module then creates an instance of it.
The test I am trying to write creates the object and then I check that it is an instance of the class I expect it to import. The issue is the isinstance check is failing.
Here is what my test looks like.
import importlib
from path.to.imported_api import SomeApi
api = importlib.import_module("path.to.imported_api").create_instance() # create_instance() is a function that returns SomeApi().
assert isinstance(api, SomeApi) # This returns false, but I am not sure why.
The reason for the difference is, that whereas both objects refer to the same module, they get different identifiers as you load a new module and bypass sys.modules. See also the explanation here: https://bugs.python.org/issue40427
A hack might be to compare the name:
assert isinstance(api.__class__.__name__, SomeApi.__name__)
There are a few things that could cause that:
So first, it could be that the api is just returning something that looks like SomeApi(). Also it coud is be that SomeApi is overwriting isinstance behaviour.

Mock scope goes beyond current test

I am mocking a module... here is my sample code
def test_validate(self):
"""Test Base Retriever Dataframe"""
sampleQuoteClass = self.sampleQuoteClass('ThisQuote')
bRet._getAsOfData = MagicMock(return_value=sampleQuoteClass)
dataAsDataFrame = bVal.validate(metaDataName='MyNewQuote')
self.assertTrue(len(dataAsDataFrame) > 0)
This works OK.
Problem is - bRet._getAsOfData is also mocked for the next tests, which incidentally resides in other test class.
This problem only occurs when all the tests are running together as a part of collection.
Sounds like you might want to patch the object instead of mocking it directly. You may need to adjust my example a bit to fit your code, but try something like this:
from mock import patch
def test_validate(self):
"""Test Base Retriever Dataframe"""
sampleQuoteClass = self.sampleQuoteClass('ThisQuote')
with patch('__main__.bRet') as mock_bRet:
mock_bRet._getAsOfData.return_value = sampleQuoteClass
dataAsDataFrame = bVal.validate(metaDataName='MyNewQuote')
self.assertTrue(len(dataAsDataFrame) > 0)
When you patch the object, the mocking will be undone and the object will "go back to normal" once the with block exits, so the mocked state will not carry over to your other tests. It is also possible to use patch as a decorator, but I have always preferred to use it as a context manager. See the documentation linked above for examples of each usage.
Also, patching can be tricky in my experience, so I would suggest you read this useful bit of documentation on "where to patch" as well.

Categories