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
specparameter to the real class. - Assign the real class to the
__class__attribute. - Use
specin 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
specorspec_setandpatch()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