82

Function foo prints to console. I want to test the console print. How can I achieve this in python?

Need to test this function, has NO return statement :

def foo(inStr):
   print "hi"+inStr

My test :

def test_foo():
    cmdProcess = subprocess.Popen(foo("test"), stdout=subprocess.PIPE)
    cmdOut = cmdProcess.communicate()[0]
    self.assertEquals("hitest", cmdOut)
Asclepius
  • 57,944
  • 17
  • 167
  • 143
sudhishkr
  • 3,318
  • 5
  • 33
  • 55
  • 1
    duplicate http://stackoverflow.com/questions/12998908/is-it-possible-to-mock-pythons-built-in-print-function tl;dr turn print into builtin function using future or assert on replaced stdout file – kwarunek Nov 17 '15 at 21:52
  • I dont want to mock anything. Infact my actual `foo` takes about 8 arguments, and it returns a json. I want to be also to test this. – sudhishkr Nov 17 '15 at 22:07

7 Answers7

97

You can easily capture standard output by just temporarily redirecting sys.stdout to a StringIO object, as follows:

import StringIO
import sys

def foo(inStr):
    print "hi"+inStr

def test_foo():
    capturedOutput = StringIO.StringIO()          # Create StringIO object
    sys.stdout = capturedOutput                   #  and redirect stdout.
    foo('test')                                   # Call unchanged function.
    sys.stdout = sys.__stdout__                   # Reset redirect.
    print 'Captured', capturedOutput.getvalue()   # Now works as before.

test_foo()

The output of this program is:

Captured hitest

showing that the redirection successfully captured the output and that you were able to restore the output stream to what it was before you began the capture.


Note that the code above in for Python 2.7, as the question indicates. Python 3 is slightly different:

import io
import sys

def foo(inStr):
    print ("hi"+inStr)

def test_foo():
    capturedOutput = io.StringIO()                  # Create StringIO object
    sys.stdout = capturedOutput                     #  and redirect stdout.
    foo('test')                                     # Call function.
    sys.stdout = sys.__stdout__                     # Reset redirect.
    print ('Captured', capturedOutput.getvalue())   # Now works as before.

test_foo()
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953
  • 5
    in Python `3.8.6` use `import io` and `io.StringIO()`. For `SocketIO` I got `AttributeError: module 'io' has no attribute 'SocketIO'`. – Enrique René Nov 21 '20 at 02:34
  • @EnriqueRené: I'm not sure I understand your comment. I've had `io.StringIO()` in the Python3 bit of the answer since that Python3 section was added back in 2017. No revision has *ever* mentioned `SocketIO`. – paxdiablo Nov 21 '20 at 06:20
  • @paxdiablo My comment was about the above @Alex comment, which from I understand I could use `SocketIO` also. I tried the suggestion and got `AttributeError`. – Enrique René Nov 27 '20 at 18:13
64

This Python 3.6 answer uses unittest.mock. It also uses a reusable helper method assert_stdout, although this helper is specific to the function being tested.

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')

Note that the mock_stdout arg is passed automatically by the unittest.mock.patch decorator to the assert_stdout method.

A general-purpose TestStdout class, possibly a mixin, can in principle be derived from the above.

For those using Python ≥3.4, contextlib.redirect_stdout also exists, but it seems to serve no benefit over unittest.mock.patch.

Asclepius
  • 57,944
  • 17
  • 167
  • 143
  • +1. Regarding contextlib: "It also has no effect on the output of subprocesses. However, it is still a useful approach for many utility scripts." and therefore cannot be reliably used. – Wtower May 28 '22 at 17:57
  • On a side note, the **expected** is the first argument on `assertEqual` – Begoodpy Aug 09 '23 at 14:56
  • @Begoodpy Not true. The [doc](https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertEqual) merely says `first` and `second`, and it is the final word. – Asclepius Aug 10 '23 at 02:52
  • @Asclepius all tests in the doc, says first, second, but `assertEqual(1, 2)` outputs is `Expected :1 Actual :2`. This is [confirmed](https://sourceforge.net/p/junit/mailman/message/3338997/) by Kent Beck - Junit cocreator - and explained in detail [here](https://stackoverflow.com/questions/2404978/why-are-assertequals-parameters-in-the-order-expected-actual) – Begoodpy Aug 16 '23 at 07:28
  • @Begoodpy People's opinions about it are irrelevant. Moreover, your claim that `assertEqual(1, 2)` output is `Expected :1 Actual :2` is totally false. It may have been true in some scenarios in the distant past, but that's gone now. With Python 3.11, the [output is `AssertionError: 1.1 != 2.2`](https://pastebin.com/6jjTdNHp) ([backup](https://archive.ph/Etzgj)). You have produced false claims, multiple times, and nothing that you say can be trusted. – Asclepius Aug 16 '23 at 16:46
  • @Asclepius I understand you are open to more comments on the subject: this [answer](https://stackoverflow.com/a/53651465/3010217) points to the BDFL discussion on when this actual/expected terminology has changed along the Python versions. As you mention `Python 3` in your answer, note that Python 3.2 uses the actual/expected order and switches to the first/second order later on. – Begoodpy Aug 17 '23 at 07:54
  • @Begoodpy Acknowledged. The answer has been updated to mention Python 3.6 which may have been the current version as of 2017 when the answer was created. – Asclepius Aug 19 '23 at 14:35
23

If you happen to use pytest, it has builtin output capturing. Example (pytest-style tests):

def eggs():
    print('eggs')


def test_spam(capsys):
    eggs()
    captured = capsys.readouterr()
    assert captured.out == 'eggs\n'

You can also use it with unittest test classes, although you need to passthrough the fixture object into the test class, for example via an autouse fixture:

import unittest
import pytest


class TestSpam(unittest.TestCase):

    @pytest.fixture(autouse=True)
    def _pass_fixtures(self, capsys):
        self.capsys = capsys

    def test_eggs(self):
        eggs()
        captured = self.capsys.readouterr()
        self.assertEqual('eggs\n', captured.out)

Check out Accessing captured output from a test function for more info.

hoefling
  • 59,418
  • 12
  • 147
  • 194
12

You can also use the mock package as shown below, which is an example from https://realpython.com/lessons/mocking-print-unit-tests.

from mock import patch

def greet(name):
    print('Hello ', name)

@patch('builtins.print')
def test_greet(mock_print):
    # The actual test
    greet('John')
    mock_print.assert_called_with('Hello ', 'John')
    greet('Eric')
    mock_print.assert_called_with('Hello ', 'Eric')
slaughter98
  • 1,759
  • 14
  • 20
2

The answer of @Acumenus says:

It also uses a reusable helper method assert_stdout, although this helper is specific to the function being tested.

the bold part seems a big drawback, thus I would do the following instead:

# extend unittest.TestCase with new functionality
class TestCase(unittest.TestCase):

    def assertStdout(self, expected_output):
        return _AssertStdoutContext(self, expected_output)

    # as a bonus, this syntactical sugar becomes possible:
    def assertPrints(self, *expected_output):
        expected_output = "\n".join(expected_output) + "\n"
        return _AssertStdoutContext(self, expected_output)



class _AssertStdoutContext:

    def __init__(self, testcase, expected):
        self.testcase = testcase
        self.expected = expected
        self.captured = io.StringIO()

    def __enter__(self):
        sys.stdout = self.captured
        return self

    def __exit__(self, exc_type, exc_value, tb):
        sys.stdout = sys.__stdout__
        captured = self.captured.getvalue()
        self.testcase.assertEqual(captured, self.expected)

this allows for the much nicer and much more re-usable:

# in a specific test case, the new method(s) can be used
class TestPrint(TestCase):

    def test_print1(self):
        with self.assertStdout("test\n"):
            print("test")

by using a straight forward context manager. (It might also be desirable to append "\n" to expected_output since print() adds a newline by default. See next example...)

Furthermore, this very nice variant (for an arbitrary number of prints!)

    def test_print2(self):
        with self.assertPrints("test1", "test2"):
            print("test1")
            print("test2")

is possible now.

NichtJens
  • 1,709
  • 19
  • 27
  • 1
    as your class is called TestCase I'm assuming that you're subclassing unittest.TestCase to expand it and that def test_print(self) is part of class TestPrintClass(TestCase) where TestCase is your extended implementation. Is that correct? -- maybe stating the obvious here, but it was something that popped up in my head while reading the code – Matthias dirickx Feb 25 '21 at 13:40
  • Absolutely correct. Sorry, for missing the `TestPrintClass` in the example. I will add it in! – NichtJens Feb 25 '21 at 15:13
  • I brought this code in but am getting an error `AttributeError: '_io.TextIOWrapper' object has no attribute 'getvalue'` – Joe_Schmoe Dec 08 '21 at 16:43
  • Strange. Which python version are you using? The docs do not even mention from which version on the method was included: `https://docs.python.org/3/library/io.html#io.StringIO.getvalue` (it does for other methods). – NichtJens Dec 08 '21 at 18:57
2

You can also capture the standard output of a method using contextlib.redirect_stdout:

import unittest
from contextlib import redirect_stdout
from io import StringIO

class TestMyStuff(unittest.TestCase):
    # ...
    def test_stdout(self):
        with redirect_stdout(StringIO()) as sout:
            my_command_that_prints_to_stdout()
        
        # the stream replacing `stdout` is available outside the `with`
        # you may wish to strip the trailing newline
        retval = sout.getvalue().rstrip('\n')

        # test the string captured from `stdout`
        self.assertEqual(retval, "whatever_retval_should_be")

Gives you a locally scoped solution. It is also possible to capture the standard error using contextlib.redirect_stderr().

András Aszódi
  • 8,948
  • 5
  • 48
  • 51
0

Another variant is leaning on the logging module rather than print(). This module also has a suggestion of when to use print in the documentation:

Display console output for ordinary usage of a command line script or program

PyTest has built-in support for testing logging messages.

Florian
  • 83
  • 1
  • 1
  • 7
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jul 31 '22 at 07:00