Mocking __iter__ with a magic mock in Python

Posted on Thu 29 March 2012 in Coding

I just ran into a tiny little problem and thought I’d share the solution.

I was writing some tests in which I was mocking a dependency using Mock. My test code was roughly equivalent to the following (based on an example in the Mock documentation on magic methods):

>>> sub1 = Mock()
>>> sub1.value = 1
>>> sub2 = Mock()
>>> sub2.value = 2
>>> m = Mock()
>>> m.__iter__ = Mock(return_value = iter([sub1, sub2]))

The point here was to mock the __iter__ method to be able to use the main mock in a list comprehension statement. The odd thing was that my tests failed, and I quickly found that of two list comprehension statements, the second would return an empty result:

>>> [x.value for x in m if x.value == 1]

>>> [x.value for x in m if x.value == 2]

An educated guess is that the first iteration consumes all elements and leaves the iterator exhausted! As it turns out, this is clearly stated in the Python documentation about iterator objects:

One notable exception is code which attempts multiple iteration passes. […] Attempting this with an iterator will just return the same exhausted iterator object used in the previous iteration pass, making it appear like an empty container.

To solve the problem, I had to use the MagicMock class instead. The documentation says:

In 0.8 the iter also gained special handling implemented with a side effect. The return value of MagicMock.iter can be any iterable object and isn’t required to be an iterator

…and gives an example similar to the following:

m = MagicMock()
m.__iter__.return_value = [sub1, sub2]

Putting it to the test:

>>> [x.value for x in m if x.value == 1]

>>> [x.value for x in m if x.value == 2]

It works! :-) A final note: It’s really 0.8 that is required; I tried with 0.8.0beta3 (which I had installed), and it didn’t work. An upgrade was needed!