37

Let's say I have a function like this:

def do_something(dict_obj):
   # access to the dict_obj then do some stuff
   eg.
   if dict_obj['doors']:
      do_something_with_doors()
   map_car_make(dict_obj['make'])
   ...

   if dict_obj['is_active']:
        do_something_else()

I want to mock the dict_obj to have is_active element and don't care the rest, how do I do that?

James Lin
  • 25,028
  • 36
  • 133
  • 233
  • 5
    Why bother mocking a simple data structure at all? Why not `dict_obj['is_active'] = True`? Usually it's _behavior_ that should be stubbed out, not data. – Matt Ball Jul 11 '16 at 04:21
  • 1
    Also, if you don't care about the values inside, you can always use `collections.defaultdict` – Dean Fenster Jul 11 '16 at 04:47
  • @MattBall because there are other logic in the function to make use of other elements in dict_obj, see updated code – James Lin Jul 11 '16 at 10:19
  • Since MagicMock does not provide this functionality out of the box, I think going with the suggestions in the other comments is usually better for readability. Using a defaultdict is still possible in your example by adding `dict_obj['make'] = MagicMock()` – phobic Jul 30 '20 at 07:35
  • I've got a similar problem but the mock needs dict-like behaviour, plus *additional* method calls that do *not* appear on a normal dict that *also* need to return different mocks... aahh @Guido_Tarsia 's answer works like this. – NeilG Jan 25 '23 at 10:01

5 Answers5

50

How to mock a dictionary in Python is a good/direct question someone else can search, so:

  1. I suggest MagicMock instead of Mock
  2. Overload the __getitem__
from unittest.mock import MagicMock

m = MagicMock()
d = {'key_1': 'value'}
m.__getitem__.side_effect = d.__getitem__

# dict behaviour
m['key_1'] # => 'value'
m['key_2'] # => raise KeyError

# mock behaviour
m.foo(42)
m.foo.assert_called_once_with(43) # => raise AssertionError

Related Questions:

=== EDIT ===

As a function for direct copy/pasting

def mock_dict(d):
  m = MagicMock()
  m.__getitem__.side_effect = d.__getitem__
  return m
Manu Artero
  • 9,238
  • 6
  • 58
  • 73
10

For anyone coming across this later, there is also mock.patch.dict which can be used as a context manager, decorator, or class decorator.

For example:

from mock import patch

foo = {}
@patch.dict(foo, {"is_active": True})
def test():
    assert foo['is_active'] == True

Or to mock os.environ which is how I came across this question:


import os
from mock import patch

@patch.dict("os.environ", {"FOO": "BAR"})
def test_env():
    assert os.environ['FOO'] == "BAR"
Peter Boone
  • 1,193
  • 1
  • 12
  • 20
  • 1
    Note: If you need to mock empty dict, don't forget to set `clear=True` parameter in `@patch.dict(...)` to ensure the mock dict is wiped first. – Jan Matějka Apr 28 '22 at 12:54
  • You can use `unittest.mock` if you do not wish to depend on `mock` external library and are using Python 3.3+ – aqua Dec 15 '22 at 21:46
5

You can use this to make a mock behave like a dictionary:

mock = mocker.MagicMock()
mock.__getitem__.side_effect = lambda x: getattr(mock, x)

With that code mock['some_property'] equals to mock.some_property. This way you can still use your autogenerated Mocks so useful for assertions, which is why I didn't like the accepted answer.

If you still need to access the methods of the dictionary then the code will need further work.

Guido Tarsia
  • 1,962
  • 4
  • 27
  • 45
  • 1
    No, wait, this might be the answer for me. So the mock here will imitate dictionary behaviour, automatically return new mocks for any key accessed in the 'dictionary' and will *also* support additional methods tacked on that are not in normal dictionaries. Just going to try this... yeah that works. – NeilG Jan 25 '23 at 09:55
0

You can use dict.get method to return the default value of your choice that can mock the existence of is_active entry.

Kevin
  • 901
  • 1
  • 7
  • 15
0

Maybe I misunderstood a question, but what about just default dict?

You can safely access any keys now.

from collections import defaultdict

dd = defaultdict(str, is_active=True)

... # Perform code calls
# And check it
assert dd['is_active'] == True

Давид Шико
  • 362
  • 1
  • 4
  • 13