Better way to mock class attribute in python unit test

Question:

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?

Asked By: Ivo van der Wijk

||

Answers:

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}):
Answered By: Martijn Pieters

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.

Answered By: Dan Keder

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()
Answered By: pymen

Here is an example how to unit-test your Base class:

  • mocking multiple class attributes of different types (ie: dict and int)
  • using the @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)
Answered By: x0s

Perhaps I’m missing something, but isn’t this possible without using PropertyMock?

with mock.patch.object(Base, 'assignment', {'bucket': 'head'}):
   # do stuff
Answered By: igniteflow

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…

Answered By: mike rodent