Monday, 20 December 2010

Mocking empty collections with FalseMock

A friend of mine described to me a PITA he had with mock - it doesn't play well with a common Python idiom:

if collection:
    for element in collection:
        do_something(element)
What he expected as default behaviour was for the mock to be iteratable as an empty collection. Instead, he got:
>>> m = mock.Mock()
>>> for x in m:
...     print x
... 
Traceback (most recent call last):
  File "", line 1, in 
TypeError: 'Mock' object is not iterable
As it turns out, there is a simple solution to this problem: all you need to do is to implement the magic method yourself.
>>> class FalseMock(mock.Mock):
...     def __nonzero__(self):
...             return False
This behaves as you would expect: It evaluates to False.
>>> m = FalseMock()
>>> bool(m)
False
It still works as every other Mock object would:
>>> m.a.b.c.d.return_value = "hi"
>>> m.a.b.c.d()
'hi'
It keeps its behaviour in all Mocks generated as a result of getting an attribute
>>> m.a.b.c.d

2 comments:

Czapel said...
This comment has been removed by the author.
Czapel said...

You can also use MagicMock that implements the magic methods. The only minor problem is that it interface is not consistent with list interface.

Namely bool(MagicMock()) == True while list(MagicMock()) == []