Reputation: 16785
I have a base class that defines a class attribute and some child classes that depend on it, e.g.
class Base(object):
assignment = dict(a=1, b=2, c=3)
I want to unittest this class with different assignments, e.g. empty dictionary, single item, etc. This is extremely simplified of course, it's not a matter of refactoring my classes or tests
The (pytest) tests I have come up with, eventually, that work are
from .base import Base
def test_empty(self):
with mock.patch("base.Base.assignment") as a:
a.__get__ = mock.Mock(return_value={})
assert len(Base().assignment.values()) == 0
def test_single(self):
with mock.patch("base.Base.assignment") as a:
a.__get__ = mock.Mock(return_value={'a':1})
assert len(Base().assignment.values()) == 1
This feels rather complicated and hacky - I don't even fully understand why it works (I am familiar with descriptors though). Does mock automagically transform class attributes into descriptors?
A solution that would feel more logical does not work:
def test_single(self):
with mock.patch("base.Base") as a:
a.assignment = mock.PropertyMock(return_value={'a':1})
assert len(Base().assignment.values()) == 1
or just
def test_single(self):
with mock.patch("base.Base") as a:
a.assignment = {'a':1}
assert len(Base().assignment.values()) == 1
Other variants that I've tried don't work either (assignments remains unchanged in the test).
What's the proper way to mock a class attribute? Is there a better / more understandable way than the one above?
Upvotes: 63
Views: 119991
Reputation: 15682
These answers seem to have missed something.
In my case I had a simple file with some constants at the top, like this:
LIB_DIR_PATH_STR = 'some_path_to_module'
After this I have a method during which I add this library to sys.path
prior to importing it:
def main():
...
sys.path.append(LIB_DIR_PATH_STR)
... but what I wanted to do in testing is to mock LIB_DIR_PATH_STR, so that it points to a non-existent path, i.e. for error-handling. Here we're not talking about mocking any classes or even methods in a script.
However, it turns out that it is possible (where my_script
has previously been imported):
with mock.patch.object(my_script, 'LIB_DIR_PATH_STR', new_callable=mock.PropertyMock(return_value=non_existent_dir_path_str)):
my_script.main()
... i.e. PropertyMock
can be instantiated with a return_value
of its own. The is not the same as specifying the return_value
for a patch in which a PropertyMock
is participating (the class of the patch will then be Mock
or maybe MagicMock
). The latter approach simply won't work for this simple "replace a string with another" type of mock: pytest will complain "expected string but got Mock".
My specific example is tangential to the question (class attributes), to show how it's done. But it can be used with class attributes too...
Upvotes: 0
Reputation: 9792
Perhaps I'm missing something, but isn't this possible without using PropertyMock
?
with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
# do stuff
Upvotes: 21
Reputation: 1868
Here is an example how to unit-test your Base
class:
dict
and int
) @patch
decorator and pytest
framework with with python 2.7+
or 3+
.# -*- coding: utf-8 -*-
try: #python 3
from unittest.mock import patch, PropertyMock
except ImportError as e: #python 2
from mock import patch, PropertyMock
from base import Base
@patch('base.Base.assign_dict', new_callable=PropertyMock, return_value=dict(a=1, b=2, c=3))
@patch('base.Base.assign_int', new_callable=PropertyMock, return_value=9765)
def test_type(mock_dict, mock_int):
"""Test if mocked class attributes have correct types"""
assert isinstance(Base().assign_dict, dict)
assert isinstance(Base().assign_int , int)
Upvotes: 7
Reputation: 6549
If your class (Queue for example) in already imported inside your test - and you want to patch MAX_RETRY attr - you can use @patch.object or simply better @patch.multiple
from mock import patch, PropertyMock, Mock
from somewhere import Queue
@patch.multiple(Queue, MAX_RETRY=1, some_class_method=Mock)
def test_something(self):
do_something()
@patch.object(Queue, 'MAX_RETRY', return_value=1, new_callable=PropertyMock)
def test_something(self, _mocked):
do_something()
Upvotes: 8
Reputation: 692
To improve readability you can use the @patch
decorator:
from mock import patch
from unittest import TestCase
from base import Base
class MyTest(TestCase):
@patch('base.Base.assignment')
def test_empty(self, mock_assignment):
# The `mock_assignment` is a MagicMock instance,
# you can do whatever you want to it.
mock_assignment.__get__.return_value = {}
self.assertEqual(len(Base().assignment.values()), 0)
# ... and so on
You can find more details at http://www.voidspace.org.uk/python/mock/patch.html#mock.patch.
Upvotes: 12
Reputation: 1123710
base.Base.assignment
is simply replaced with a Mock
object. You made it a descriptor by adding a __get__
method.
It's a little verbose and a little unnecessary; you could simply set base.Base.assignment
directly:
def test_empty(self):
Base.assignment = {}
assert len(Base().assignment.values()) == 0
This isn't too safe when using test concurrency, of course.
To use a PropertyMock
, I'd use:
with patch('base.Base.assignment', new_callable=PropertyMock) as a:
a.return_value = {'a': 1}
or even:
with patch('base.Base.assignment', new_callable=PropertyMock,
return_value={'a': 1}):
Upvotes: 57