Mock nested properties with python-mock

Python's mock library (part of stdlib in 3.3+) is a great tool for writing concise tests. Its documentation is very good, and rewards multiple reads - but I found one thing that wasn't totally clear, even after looking through a few times. I wanted to use PropertyMock to mock nested Properties. Specifically, I had patched the python Gnome-introspection wrapper for libsoup at the top level Soup objcet, and I also wanted to replace one of its nested constant properties, Soup.MemoryUse.COPY with a sentinel that I controlled, for later comparison.

The idiom for PropertyMock is to assign a PropertyMock to the type object of the Mock object whose property you want control of. What I found is that because Mocks auto-create properties, it's possible to do nested mocking in one line, like this:

import mock
m = mock.Mock()
type(m.a.b.c).d = mock.PropertyMock(return_value = mock.sentinel.my_value)
assert(m.a.b.c.d == mock.sentinel.my_value)

So my soup example looks roughly like this (mixing testing and tested code, and repeating literals for brevity):

json_body = "{}"
with patch(gi.repository.Soup) as mock_soup:
    from gi.repository import Soup
    type(mock_soup.MemoryUse).COPY = mock.PropertyMock(return_value=mock.sentinel.COPY)

    # tested code:
    message = Soup.Message.new("POST", "http://fake.com/api")
    message.set_request('application/json', Soup.MemoryUse.COPY, json_body, len(json_body))

    # checking:
    assert(mock_soup.mock_calls == [mock.call.Message.new("POST", "http://fake.com/api"), 
                                    mock.call.Message.new().set_request('application/json', 
                                                                        sentinel.COPY # <--- this was the point
                                                                        "{}", 2)])

It's often possible to think of a shorter, clearer use of the mock library after revisiting a problem, but so far this still seems good. Let me know in the comments if you have a suggestion for improvements.

Comments

Comments powered by Disqus