53

I've tried

from mock import Mock
import __builtin__

__builtin__.print = Mock()

But that raises a syntax error. I've also tried patching it like so

@patch('__builtin__.print')
def test_something_that_performs_lots_of_prints(self, mock_print):

    # assert stuff

Is there any way to do this?

Asclepius
  • 57,944
  • 17
  • 167
  • 143
aychedee
  • 24,871
  • 8
  • 79
  • 83

12 Answers12

55

I know that there is already an accepted answer but there is simpler solution for that problem - mocking the print in python 2.x. Answer is in the mock library tutorial: http://www.voidspace.org.uk/python/mock/patch.html and it is:

>>> from StringIO import StringIO
>>> def foo():
...     print 'Something'
...
>>> @patch('sys.stdout', new_callable=StringIO)
... def test(mock_stdout):
...     foo()
...     assert mock_stdout.getvalue() == 'Something\n'
...
>>> test()

Of course you can use also following assertion:

self.assertEqual("Something\n", mock_stdout.getvalue())

I have checked this solution in my unittests and it is working as expected. Hope this helps somebody. Cheers!

Krzysztof Czeronko
  • 571
  • 1
  • 4
  • 7
  • This doesn't work for for `pytest` in Python 2 (sorry for the formatting in comments). "def test_status(mocker): mocker_print = mocker.patch("sys.stdout", new_callable=StringIO) > print("yes") E TypeError: unicode argument expected, got 'str'" – Yaroslav Nikitenko Jan 20 '21 at 15:56
  • As of Python 3, you need to import StringIO from io like so: `from io import StringIO`. – FiddleStix Feb 27 '23 at 17:41
43

This is a much simpler Python 3 solution -- it's easier to use unittest.mock directly on the builtin print function, rather than fiddling around with sys.stdout:

from unittest.mock import patch, call

@patch('builtins.print')
def test_print(mocked_print):
    print('foo')
    print()

    assert mocked_print.mock_calls == [call('foo'), call()]

Alternative example, evaluating args and kwargs directly:

@patch('builtins.print')
def test_print(mocked_print):
    print('foo', 'bar', file=sys.stderr)

    assert mocked_print.call_args.args == ('foo', 'bar')
    assert mocked_print.call_args.kwargs == dict(file=sys.stderr)
Peterino
  • 15,097
  • 3
  • 28
  • 29
Tom
  • 42,844
  • 35
  • 95
  • 101
  • 1
    This is the only way that makes sense for Python 3. This way we can perform assertions on mocks which is a major advantage for testing. – Artur Oct 28 '19 at 09:47
  • Thanks for this answer, which is what I ended up using. However I noticed that it's far better to limit the patch to the context where I expect print to be used (the module it's imported in), otherwise I'll be patching the builtin that pytest, pdb, etcetera use as well -- for example that prevented me from breaking into the test code. So assuming the call is in module `myapp.mymod`, I'd patch `myapp.mymod.print` instead. – Matteo Mecucci Dec 30 '21 at 13:59
  • How do you check a call to print() that supplies argument file=sys.stderr? – chrisinmtown May 05 '22 at 13:02
  • `printed = '\n'.join(x.args[0] for x in mocked_print.mocks_calls)` if you would like to recreate what was printed (assuming you only pass one arg at a time to print, cause you probably use f-strings. – run_the_race Jul 14 '22 at 14:16
18

print is a keyword in python 2.x, using it as attribute raises a SyntaxError. You can avoid that by using from __future__ import print_function in the beginning of the file.

Note: you can't simply use setattr, because the print function you modified doesn't get invoked unless the print statement is disabled.

Edit: you also need to from __future__ import print_function in every file you want your modified print function to be used, or it will be masked by the print statement.

quantum
  • 3,672
  • 29
  • 51
  • So I could `setattr(__builtin__, 'print', Mock())` and then disable the print statement somehow? Or do you mean disable it by importing the Python 3 print function? It would be nice to be able to do this entirely on the test side without modifying the code under test. – aychedee Oct 21 '12 at 15:38
  • @aychedee In all the source files that need to use your modified `print` function, you need to disable it by importing the Python 3 `print` function. `setattr` used in lqc's way will not work because it's masked by the `print` statement. – quantum Oct 21 '12 at 15:50
  • 1
    Okay, cheers, thanks xiaomao. That works. So the answer is that this is only possible by using the Python 3 print function, and without that it doesn't work. – aychedee Oct 21 '12 at 16:07
  • Could you please provide a full example how to test the actual call to `print`? – Yaroslav Nikitenko Jan 20 '21 at 16:00
6
from unittest.mock import patch


def greet():
    print("Hello World")


@patch('builtins.print')
def test_greet(mock_print):
    greet()
    mock_print.assert_called_with("Hello World!")
Vlad Bezden
  • 83,883
  • 25
  • 248
  • 179
3

If you want to stick with the print statement from 2.x as opposed to the print() function from 2.x, you could mock your sys.stdout instead.

Write a dummy "file", perhaps in about this way:

class Writable(object):
    """Class which has the capability to replace stdout."""
    newwrite = None
    def __init__(self, oldstdout, newwrite=None):
        self.oldstdout = oldstdout
        if newwrite is not None:
            self.newwrite = newwrite
    def write(self, data):
        self.newwrite(self.oldstdout, data)
    @classmethod
    def subclass(cls, writefunc):
        newcls = type('', (cls,),
            dict(write=lambda self, data: writefunc(self.oldstdout, data)
        return newcls

This class expects to be combined with a writing function which gets the printed data. This writing function is supposed to take 2 arguments: the first one with the "old stdout" to be used for printing at the end, and a further one for the data.

Let's take

def mywrite(sink, data):
    sink.write(data.encode("hex"))

for that.

Now you can do

import sys
sys.stdout = Writable(sys.stdout, mywrite)

or you can do

@Writable.subclass
def mywritable(sink, data)
    sink.write(data.encode("hex"))

sys.stdout = mywritable(sys.stdout)

The 2nd version is a bit trickier: it creates a subclass of the Writable with the help of a decorator function which turns the given function into a method of the new class created instead and put into the name where the given function comes from.

After that, you have a new class which can be instantiated with the "old stdout" as argument and can replace sys.stdout after that.

glglgl
  • 89,107
  • 13
  • 149
  • 217
3

My version.

In the tested program(ex: pp.py):

from __future__ import print_function

def my_func():
    print('hello')

In the test program:

def test_print(self):
    from pp import my_func
    from mock import call
    with mock.patch('__builtin__.print') as mock_print:
       my_func()
       mock_print.assert_has_calls(
            [
                call('hello')
            ]
        )
Chien-Wei Huang
  • 1,773
  • 1
  • 17
  • 27
1
import mock
import sys

mock_stdout = mock.Mock()
sys.stdout = mock_stdout
print 'Hello!'
sys.stdout = sys.__stdout__

print mock_stdout.mock_calls
[call.write('Hello!'), call.write('\n')]
sgjurano
  • 11
  • 1
  • 1
    It would be helpful if you explained what you did here and why the code in the question didn't work. – blalasaadri Oct 23 '14 at 14:13
  • We are switching sys.stdout by our mock object, and when we print some text, it may be found in mock_stdout calls. At the end we returns sys.stdout to original state. – sgjurano Oct 23 '14 at 14:45
1

This is a v3 version of @KC's answer.

I didn't want to mock print because I specifically wanted to see the output as a whole, not check out individual calls so StringIO makes more sense to me.

from io import StringIO
from unittest.mock import patch

def foo():
    print ('Something')

def test():
    with patch('sys.stdout', new_callable=StringIO) as buffer:
        foo()
    fake_stdout = buffer.getvalue()

    #print() is back!
    print(f"fake_stdout:{fake_stdout}")
    assert fake_stdout == 'Something\n'

test()

warning:

for the duration of the patch, mocking stdout plays badly with using pdb.set_trace(). I commented out with..., added if True: to keep the indentation, debugged my script and put back the batch once I fixed my error.

    #with patch('sys.stdout', new_callable=StringIO) as buffer:
    if True:
        foo()
    ...
JL Peyret
  • 10,917
  • 2
  • 54
  • 73
0

First, the module is called __builtins__ and you don't need to import it.

Now, in Python 2 print is a keyword so you can't use it as an attribute name directly. You can use setattr/getattr to workaround it:

getattr(__builtins__, "print")

Another option is to use from __future__ import print_function which changes how Python parses the module to Python 3 syntax.

lqc
  • 7,434
  • 1
  • 25
  • 25
  • `__builtin__ == __builtins__ >>> True` in 2.7.3. So without using the Python 3 print function the answer is no? – aychedee Oct 21 '12 at 15:36
0

As lcq says, print is a keyword. So, think about what it would mean if you were actually successful in patching/mocking print under Python 2.7.3. You would have code like this:

print "Hi."

turning into:

<MagicMock id='49489360'> "Hi."

MagicMock objects cannot be accessed this way, so you would get a syntax error.

So... Yeah. You can only mock the Python3 print function or sys.stdout.

dbn
  • 13,144
  • 3
  • 60
  • 86
0

This Python 3 example builds upon the Python 2 answer by Krzysztof. It uses unittest.mock. It uses a reusable helper method for making the assertion.

import io
import unittest
import unittest.mock

from .solution import fizzbuzz


class TestFizzBuzz(unittest.TestCase):

    @unittest.mock.patch('sys.stdout', new_callable=io.StringIO)
    def assert_stdout(self, n, expected_output, mock_stdout):
        fizzbuzz(n)
        self.assertEqual(mock_stdout.getvalue(), expected_output)

    def test_only_numbers(self):
        self.assert_stdout(2, '1\n2\n')
Asclepius
  • 57,944
  • 17
  • 167
  • 143
0

Can also used assert_any_call

from unittest.mock import patch, call

@patch('builtins.print')
def test_print(mocked_print):
    print('foo')
    print()

   mocked_print.assert_any_call('foo')
Blundell
  • 75,855
  • 30
  • 208
  • 233