Unable to patch object inside function - python

I am new in Python. I would like to write an unit test for the following function:
from common.ds_factory import DSFactory
class MyClass:
def load(self, parsed_file_key):
ds = DSFactory.getDS()
...
Now I am unable to mock DSFactory using #patch(my_class.DSFactory) as I am using DSFactory inside the function.

You need to patch DSFactory for module that uses it. As mentioned in comments, assuming MyClass is defined in file my_module.py you patch it by using module name: #patch("my_module.DSFactory").
There's a catch - your module has to be importable, so it requires you to create modules.

Related

Using unittest.mock's patch in same module, getting "does not have the attribute" when patching via "__main__.imported_obj"

I have what should've been a simple task, and it has stumped me for a while. I am trying to patch an object imported into the current module.
Per the answers to Mock patching from/import statement in Python
I should just be able to patch("__main__.imported_obj"). However, this isn't working for me. Please see my below minimal repro (I am running the tests via pytest):
Minimal Repro
This is run using Python 3.8.6.
from random import random
from unittest.mock import patch
import pytest
#pytest.fixture
def foo():
with patch("__main__.random"):
return
def test(foo) -> None:
pass
When I run this code using PyCharm, I get an AttributeError:
AttributeError: <module '__main__' from '/Applications/PyCharm.app/Contents/plugins/python/helpers/pycharm/_jb_pytest_runner.py'> does not have the attribute 'random'
Furthermore, when I enter debugger mode in the line before the with patch, I see the attribute __main__ is not defined. I am not sure if it needs to be defined for patch to work its magic.
NOTE: I know I can use patch.object and it becomes much easier. However, I am trying to figure out how to use patch in this question.
Research
Unable to mock open, even when using the example from the documentation
This question is related because it's both a similar error message and use case. Their solution was to use builtins instead of __main__, but that's because they were trying to patch a built-in function (open).
You are assuming that the module the test is running in is __main__, but that would only be the case if it were called via main. This is usually the case if you are using unittest. With pytest, the tests live in the module they are defined in.
You have to patch the current module, the name of which is accessible via __name__, instead of assuming a specific module name:
from random import random
from unittest.mock import patch
import pytest
#pytest.fixture
def foo():
with patch(__name__ + ".random"):
yield

Mocking imported class in Python

I am trying to mock a class which is called in a module which imports said class, which I want to test.
# application.py
from my_module.my_submodule import MyClass
def my_function(var1):
instance = MyClass()
instance.some_function(var1)
and my testing file
# test_application.py
import mock
import application
def test_my_function():
with mock.patch('my_module.my_submodule.MyClass') as MockClass:
application.my_function(var1)
MockClass.assert_called()
This gives an error saying MockClass was not called.
Now, by looking at this question: Why python mock patch doesn't work?, I was inspired to change the application.py import to this
# application.py
import my_module.my_submodule as mysub
def my_function(var1):
instance = mysub.MyClass()
instance.some_function(var1)
that is, I don't directly import the class that I want to mock in the test. Now it works.
My question is, if this is working as intended, or I am doing something wrong in the original way? Is it really necessary to always import modules like this, if I want to mock a class used in a module I want to test?
Yes it is working as intended, but you patched the wrong target.
Try patching application.MyClass.
application is not using my_module.my_submodule.MyClass anywhere,
but MyClass alias instead.
No, you don't have to import modules in some specific way to be able to mock/patch some name. What you have to do is to look at how that name is used at runtime, when you want it patched.
If the test is in a separate module from the module being tested, and the tested module, application in this case, imports the name and uses it directly like in from a import MyClass, then in the test module you import application and patch application.MyClass. If instead application uses import a and then calls a.MyClass() you have to patch application.a.MyClass.
So you adapt the patch target to the concrete naming scenario in the application module. For example if your test is in the same application module, and the class is being used as MyClass, you need to patch __main__.MyClass.
It is however true, that writing code in certain ways can make it easier to patch when you are testing. A good example is when the entity to mock is a function parameter. Just call the function with a mock argument.
If you find that patching is too convoluted or looks impossible, try to rewrite the code in the application so that it is more "testable".
For another example as reference, see Where to patch

monkeypatch function in module whose namespace was overwritten

I am trying to monkeypatch a function in an external module I use, but monkeypatch can't seem to access the function because the namespace of the module gets overwritten on import.
Concretely, I use a Bio.PDB.PDBList.PDBList object (biopython module) in my code, and I am trying to patch _urlretrieve in Bio.PDB.PDBList to prevent calls to the internet and instead get files from a local directory, without having to mock the instance methods of PDBList which would be substantially more work. But when I try the naïve:
m.setattr("Bio.PDB.PDBList._urlretrieve", mock_retrieve)
pytest complains:
AttributeError: 'type' object at Bio.PDB.PDBList has no attribute '_urlretrieve'
On further inspection of Bio.PDB, I can see that the module namespace .PDBList seems to be overwritten by the class .PDBList.PDBList:
# Download from the PDB
from .PDBList import PDBList
So that would explain why pytest sees Bio.PDB.PDBList as a type object with no attribute _urlretrieve. My question is, is there any way to get monkeypatch to patch this 'hidden' function?
Concrete example of usage of PDBList class:
from Bio.PDB.PDBList import PDBList
_pdblist = PDBList()
downloaded_file = _pdblist.retrieve_pdb_file('2O8B', pdir='./temp', file_format='pdb')
You are right - since the PDBList class has the same name as the module Bio.PDB.PDBList, after import Bio.PDB.PDBList you won't be able to access the module by its name (shadowing problem). However, you can still grab the imported module object from the loaded modules cache and monkeypatch that:
import sys
from unittest.mock import Mock
import Bio.PDB.PDBList
def test_spam(monkeypatch):
assert isinstance(Bio.PDB.PDBList, type)
with monkeypatch.context() as m:
m.setattr(sys.modules['Bio.PDB.PDBList'], '_urlretrieve', Mock())
...

Dynamic Import - Python - Call function not working

I am having some trouble with dynamically importing Classes and attempting to run functions in said classes. This is my problem, specifically.
I have a python script dyn_imports.py in a director called dynamic_imports. Inside this folder is a subdir called scripts. In this scripts directory there is an __init__.py and a python class called AbhayScript.py. In the class AbhayScript, there is a function called say_hello()
My objective is: From dyn_imports.py, I would like to be able to import scripts.AbhayScript and call the function say_hello() in AbhayScript
So far, I have attempted a variety of options including __import__, importlib.import_module and pydoc.locate. All of these options give me access to the module AbhayScript but when I try a getattrs() or try to call the object, I get an error stating that its not callable or has no attribute.
dyn_imports.py - One of my experiments
myclass = 'scripts.AbhayScript'
import importlib
mymod = importlib.import_module(myclass)
mod,newclass = myclass.rsplit('.',1)
ncls = getattr(mod,newclass) #throws an AttributeError that 'str' object has no attribute
AbhayScript.py code
class AbhayScript(object):
def say_hello(self):
print 'Hello Abhay Bhargav'
The directory structure is as follows
The init.py in the scripts folder is empty.
I have attempted nearly all the solutions in Stackoverflow, but I am a little flummoxed by this one. What am I doing wrong?
I realize what I was doing wrong. I was importing the module and not referencing the class in the getattr function. I made the class declaration explicit in the __import__ function and in the getattr function and I was subsequently able to gain access to the functions in the class
Here's the code in dyn_imports.py
myclass = 'scripts.AbhayScript'
mod = __import__(myclass, fromlist = ['AbhayScript']) #class explicit
klass = getattr(mod, 'AbhayScript') #explicit class
klass().say_hello() #calls the function as desired

Patch - Why won't the relative patch target name work?

I've imported a class from a module, but when I try to patch the class name without it's module as a prefix I get a type error:
TypeError: Need a valid target to patch. You supplied: 'MyClass'
For example, the following code gives me the above error:
import unittest
from mock import Mock, MagicMock, patch
from notification.models import Channel, addChannelWithName, deleteChannelWithName, listAllChannelNames
class TestChannel(unittest.TestCase):
#patch("Channel")
def testAddChannelWithNamePutsChannel(self, *args):
addChannelWithName("channel1")
Channel.put.assert_called_with()
While this second version of the code does not give me the type error:
import unittest
from mock import Mock, MagicMock, patch
from notification.models import Channel, addChannelWithName, deleteChannelWithName, listAllChannelNames
class TestChannel(unittest.TestCase):
#patch("notification.models.Channel")
def testAddChannelWithNamePutsChannel(self, *args):
addChannelWithName("channel1")
Channel.put.assert_called_with()
Why is that? Why can I reference Channel as just "Channel" in other places, yet for the patch I need the module prefix not to get an error? Also, I have a feeling that giving the full module prefix isn't working either because when I call Channel.put.assert_called_with() I get the error that assert_called_with is not an attribute of Channel.put. Can someone explain what's going on? Thank you much!
The patch decorator requires the target to be a full dotted path, as stated in the documentation:
target should be a string in the form ‘package.module.ClassName’. The target is imported and the specified object replaced with the new object, so the target must be importable from the environment you are calling patch from. The target is imported when the decorated function is executed, not at decoration time.
"Channel" is just a string, and patch does not have enough information to find the proper class. This is not the same as the name Channel you use elsewhere, which is imported at the top of the module.
The second test fails because Channel gets imported in the test module then patch replaces Channel in notification.models with a mock object. What patch actually does is change the object the name Channel used inside notification.models point to. The name Channel in the test module has already been defined, so it is not affected. This is actually better explained here: https://docs.python.org/3/library/unittest.mock.html#where-to-patch
To access the patched version of your object, you can either access the module directly:
import unittest
from unittest.mock import patch
from notification.models import Channel, addChannelWithName
from notification import models
class TestChannel1(unittest.TestCase):
#patch("notification.models.Channel")
def testAddChannelWithNamePutsChannel(self, *args):
addChannelWithName("channel1")
models.Channel.put.assert_called_with("channel1")
Or use the patched version passed as an extra argument to the decorated function:
class TestChannel2(unittest.TestCase):
#patch("notification.models.Channel")
def testAddChannelWithNamePutsChannel(self, mock_channel):
addChannelWithName("channel1")
mock_channel.put.assert_called_with("channel1")
If you just want to quickly patch a single method on an object, it's usually easier to use the patch.object decorator:
class TestChannel3(unittest.TestCase):
#patch.object(Channel, 'put')
def testAddChannelWithNamePutsChannel(self, *arg):
addChannelWithName("channel1")
Channel.put.assert_called_with("channel1")

Categories