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: http://www.voidspace.org.uk/python/mock/patch.html#id1
To access the patched version of your object, you can either access the module directly:
import unittest
from 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")