I want to test the function is_myclass
. Please help me understand how to write a successful test.
def is_myclass(obj):
"""This absurd stub is a simplified version of the production code."""
isinstance(obj, MyClass)
MyClass()
Docs
The Python Docs for unittest.mock illustrate three ways of addressing the isinstance
problem:
- Set the
spec
parameter to the real class. - Assign the real class to the
__class__
attribute. - Use
spec
in the patch of the real class.
__class__
Normally the
__class__
attribute of an object will return its type. For a mock object with a spec,__class__
returns the spec class instead. This allows mock objects to pass isinstance() tests for the object they are replacing / masquerading as:>>> mock = Mock(spec=3) >>> isinstance(mock, int) True
__class__
is assignable to, this allows a mock to pass anisinstance()
check without forcing you to use a spec:>>> mock = Mock() >>> mock.__class__ = dict >>> isinstance(mock, dict) True
[...]
If you use
spec
orspec_set
andpatch()
is replacing a class, then the return value of the created mock will have the same spec.>>> Original = Class >>> patcher = patch('__main__.Class', spec=True) >>> MockClass = patcher.start() >>> instance = MockClass() >>> assert isinstance(instance, Original) >>> patcher.stop()
Tests
I have written five tests each of which attempts firstly to reproduce each of the three solutions and secondly to carry out a realistic test of the target code. The typical pattern is assert isinstance
followed by a call to is_myclass
.
All tests fail.
Test 1
This is a close copy of the example provided in the docs for the use of spec
. It
differs from the docs by using spec=<class>
instead of spec=<instance>
. It passes
a local assert test but the call to is_myclass
fails because MyClass
is not mocked.
This is equivalent to Michele d’Amico’s answer to the similar question in isinstance and Mocking .
Test 2
This is the patched equivalent of test 1. The spec
argument fails to set the __class__
of the mocked MyClass and the test fails the local assert isinstance
.
Test 3
This is a close copy of the example provided in the docs for the use of __class__
. It passes
a local assert test but the call to is_myclass
fails because MyClass
is not mocked.
Test 4
This is the patched equivalent of test 3. The assignment to __class__
does set the __class__
of the mocked MyClass
but this does not change its type and so the test fails the local assert isinstance
.
Test 5
This is a close copy of the use of spec
in a call to patch. It passes the local assert test but only by virtue of accessing a local copy of MyClass. Since this local variable is not used within is_myclass
the call fails.
Code
This code was written as a stand alone test module intended to be run in the PyCharm IDE. You may need to modify it to run in other test environments.
module temp2.py
import unittest
import unittest.mock as mock
class WrongCodeTested(Exception):
pass
class MyClass:
def __init__(self):
"""This is a simplified version of a production class which must be mocked for unittesting."""
raise WrongCodeTested('Testing code in MyClass.__init__')
def is_myclass(obj):
"""This absurd stub is a simplified version of the production code."""
isinstance(obj, MyClass)
MyClass()
class ExamplesFromDocs(unittest.TestCase):
def test_1_spec(self):
obj = mock.Mock(spec=MyClass)
print(type(MyClass)) # <class 'type'>
assert isinstance(obj, MyClass) # Local assert test passes
is_myclass(obj) # Fail: MyClass instantiated
def test_2_spec_patch(self):
with mock.patch('temp2.MyClass', spec=True) as mock_myclass:
obj = mock_myclass()
print(type(mock_myclass)) # <class 'unittest.mock.MagicMock'>
print(type(MyClass)) # <class 'unittest.mock.MagicMock'>
assert isinstance(obj, MyClass) # Local assert test fails
def test_3__class__(self):
obj = mock.Mock()
obj.__class__ = MyClass
print(type(MyClass)) # <class 'type'>
isinstance(obj, MyClass) # Local assert test passes
is_myclass(obj) # Fail: MyClass instantiated
def test_4__class__patch(self):
Original = MyClass
with mock.patch('temp2.MyClass') as mock_myclass:
mock_myclass.__class__ = Original
obj = mock_myclass()
obj.__class__ = Original
print(MyClass.__class__) # <class 'temp2.MyClass'>
print(type(MyClass)) # <class 'unittest.mock.MagicMock'>
assert isinstance(obj, MyClass) # Local assert test fails
def test_5_patch_with_spec(self):
Original = MyClass
p = mock.patch('temp2.MyClass', spec=True)
MockMyClass = p.start()
obj = MockMyClass()
print(type(Original)) # <class 'type'>
print(type(MyClass)) # <class 'unittest.mock.MagicMock'>
print(type(MockMyClass)) # <class 'unittest.mock.MagicMock'>
assert isinstance(obj, Original) # Local assert test passes
is_myclass(obj) # Fail: Bad type for MyClass