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