Mocking Print

Background

I wanted to check to make sure I was sending the right output to the screen so I thought I would use mock to catch what I was sending to it. These are some notes about what happened.

Mocking print

If you try to patch print with MagicMock, here is what you get.


from mock import MagicMock, patch, call

mock_print = MagicMock()
try:
with patch('print', mock_print):
print 'test'
except TypeError as error:
print error

Need a valid target to patch. You supplied: 'print'

So it looks like 'print' is not the right thing to patch. Maybe you need to call it a built-in funtion:


with patch('__builtin__.print', mock_print):
print 'test'

print mock_print.mock_calls

test
[]

What if you call it as a function?


with patch('__builtin__.print', mock_print):
print( 'test')

print mock_print.mock_calls

test
[]

So neither of those raise an error, but they also do not manage to mock print. Well, if you look at the description for print, it turns out that the print function has the signature:


print(*objects, sep=' ', end='\n', file=sys.stdout)

It also says that the function call is not normally available unless you import it from the future.

But, presuming that the print statement works the same way as the function call, what happens if you mock sys.stdout?

Mocking sys.stdout

This doesn't answer the question of why I can't mock print directly, but maybe mocking sys.stdout will work instead.


with patch('sys.stdout', mock_print):
print 'test'

expected = [call('test')]
actual = mock_print.mock_calls

try:
assert actual == expected, "Expected: {0} Actual: {1}".format(expected,
actual)
except AssertionError as error:
print error

Expected: [call('test')] Actual: [call.write('test'), call.write('\n')]

It looks like the mock works this time, but it did not return what I was expecting -- print makes two calls to stdout.write, the first is the string you pass it which it then follows with a newline character. Given this slightly more complete understanding of print:


# create some output to send to print
lines = "a b c".split()

# reset the mock so the previous calls are gone
mock_print.reset_mock()
with patch('sys.stdout', mock_print):
for line in lines:
print line

expected = []
for line in lines:
expected.append(call.write(line))
expected.append(call.write('\n'))

actual = mock_print.mock_calls

try:
assert actual == expected, "Expected: {0} Actual: {1}".format(expected,
actual)
except AssertionError as error:
print error

Conclusion

Not earth-shattering, but I thought it was interesting that even after using Python for years, something as basic as print can yield something new if looked at more closely. It was also useful to see how mock can be used to discover the calls that are being made on an object, not just to test that expected calls are being made.