126

I'm writing tests for a function like next one:

def foo():
    print 'hello world!'

So when I want to test this function the code will be like this:

import sys
from foomodule import foo
def test_foo():
    foo()
    output = sys.stdout.getline().strip() # because stdout is an StringIO instance
    assert output == 'hello world!'

But if I run nosetests with -s parameter the test crashes. How can I catch the output with unittest or nose module?

Antoine
  • 4,456
  • 4
  • 44
  • 51
Pedro Valencia
  • 1,277
  • 2
  • 9
  • 3
  • 2
    [`with mock.patch('sys.stdout', new_callable=StringIO.StringIO):`](http://www.voidspace.org.uk/python/mock/patch.html#patch) https://pypi.python.org/pypi/mock/ – n611x007 Jan 22 '15 at 10:17

13 Answers13

133

I use this context manager to capture output. It ultimately uses the same technique as some of the other answers by temporarily replacing sys.stdout. I prefer the context manager because it wraps all the bookkeeping into a single function, so I don't have to re-write any try-finally code, and I don't have to write setup and teardown functions just for this.

import sys
from contextlib import contextmanager
from StringIO import StringIO

@contextmanager
def captured_output():
    new_out, new_err = StringIO(), StringIO()
    old_out, old_err = sys.stdout, sys.stderr
    try:
        sys.stdout, sys.stderr = new_out, new_err
        yield sys.stdout, sys.stderr
    finally:
        sys.stdout, sys.stderr = old_out, old_err

Use it like this:

with captured_output() as (out, err):
    foo()
# This can go inside or outside the `with` block
output = out.getvalue().strip()
self.assertEqual(output, 'hello world!')

Furthermore, since the original output state is restored upon exiting the with block, we can set up a second capture block in the same function as the first one, which isn't possible using setup and teardown functions, and gets wordy when writing try-finally blocks manually. That ability came in handy when the goal of a test was to compare the results of two functions relative to each other rather than to some precomputed value.

schneiderfelipe
  • 375
  • 1
  • 8
  • 19
Rob Kennedy
  • 161,384
  • 21
  • 275
  • 467
  • This has worked really well for me in [pep8radius](https://github.com/hayd/pep8radius). Recently however, I had used this again and get the following error when printing `TypeError: unicode argument expected, got 'str'` (the type passed to print (str/unicode) is irrelevant). – Andy Hayden Oct 11 '14 at 04:24
  • 10
    Hmmm it may be that in python 2 we want `from io import BytesIO as StringIO` and in python 3 just `from io import StringIO`. Seemed to fix the issue in my tests I think. – Andy Hayden Oct 11 '14 at 04:39
  • 4
    Ooop, just to finish, apologies for so many messages. Just to clarify for people finding this: python3 use io.StringIO, python 2 use StringIO.StringIO! Thanks again! – Andy Hayden Oct 11 '14 at 04:46
  • 1
    Why are all the examples here calling `strip()` on the `unicode` returned from `StringIO.getvalue()`? – Palimondo Jun 04 '17 at 11:52
  • @Palimondo, it removes the trailing line break. – Rob Kennedy Jun 04 '17 at 13:00
  • @RobKennedy But it also removes newlines that are at the beginning and those at the end and are legitimate part of the output, right? – Palimondo Jun 04 '17 at 17:09
  • They're evidently not, in this case, @Palimondo. Note that the test in the question did the same thing. – Rob Kennedy Jun 04 '17 at 17:19
  • Any way to make this work with `from sys import stderr`, instead of just with `import sys`? – Vedran Šego Jun 09 '17 at 16:04
  • 1
    No, @Vedran. This relies on rebinding the name that belongs to `sys`. With your import statement, you're creating a *local* variable named `stderr` that received a *copy* of the value in `sys.stderr`. Changes to one are not reflected in the other. – Rob Kennedy Jun 09 '17 at 17:29
  • @RobKennedy I understand that. My question was: is there a way to capture `stderr` in that case too? – Vedran Šego Jun 10 '17 at 16:59
  • Sounds like a good question to ask on Stack Overflow, @Vedran. – Rob Kennedy Jun 10 '17 at 17:01
  • How do I flush the out buffer? `out.flush()` seems to do nothing (python 2.7) – Gulzar May 13 '19 at 06:13
  • @Gulzar, `StringIO` has no buffer to flush. It simply _is_ the buffer. If code has run `print 'foo'` while the above context manager was active, then `foo` will be in `out`. – Rob Kennedy May 13 '19 at 21:01
  • I have a problem with this. When I use the context manager for the second time, it doesn't capture anything. What am I doing wrong? – Hubert Grzeskowiak May 22 '19 at 06:59
  • From that meager description, I couldn't possibly know what you're doing wrong, @Hubert. Have you considered posting a question on Stack Overflow? – Rob Kennedy May 22 '19 at 16:23
  • `ModuleNotFoundError: No module named 'StringIO'`. Python 3: `from io import StringIO` – StressedBoi69420 Sep 29 '21 at 15:33
66

If you really want to do this, you can reassign sys.stdout for the duration of the test.

def test_foo():
    import sys
    from foomodule import foo
    from StringIO import StringIO

    saved_stdout = sys.stdout
    try:
        out = StringIO()
        sys.stdout = out
        foo()
        output = out.getvalue().strip()
        assert output == 'hello world!'
    finally:
        sys.stdout = saved_stdout

If I were writing this code, however, I would prefer to pass an optional out parameter to the foo function.

def foo(out=sys.stdout):
    out.write("hello, world!")

Then the test is much simpler:

def test_foo():
    from foomodule import foo
    from StringIO import StringIO

    out = StringIO()
    foo(out=out)
    output = out.getvalue().strip()
    assert output == 'hello world!'
Shane Hathaway
  • 2,141
  • 1
  • 13
  • 9
  • 12
    Note: Under python 3.x the `StringIO` class must now be imported from the `io` module. `from io import StringIO` works in python 2.6+. – Bryan P May 08 '13 at 22:26
  • 3
    If you use `from io import StringIO` in python 2, you get a `TypeError: unicode argument expected, got 'str'` when printing. – matiasg May 14 '14 at 14:23
  • 9
    Quick note: In python 3.4, you can use the [contextlib.redirect_stdout](https://docs.python.org/3.4/library/contextlib.html#contextlib.redirect_stdout) context manager to do this in a way that is exception safe: `with redirect_stdout(out):` – Lucretiel Nov 26 '14 at 22:49
  • 2
    You don't need to do `saved_stdout = sys.stdout`, you always have a magic ref to this at `sys.__stdout__`, eg, you only need `sys.stdout = sys.__stdout__` in your cleanup. – ThorSummoner Oct 27 '15 at 23:04
  • @ThorSummoner Thanks, this just simplified some of my tests... for [scuba](https://github.com/JonathonReinhart/scuba) which I see you've starred....small world! – Jonathon Reinhart Jan 26 '16 at 01:17
  • It is worth mentioning that the reason for reassigning `sys.stdout` is the fact that it is write-only (its mode is `w`), so it is unreadable. – MROB May 15 '20 at 09:22
48

Since version 2.7, you do not need anymore to reassign sys.stdout, this is provided through buffer flag. Moreover, it is the default behavior of nosetest.

Here is a sample failing in non buffered context:

import sys
import unittest

def foo():
    print 'hello world!'

class Case(unittest.TestCase):
    def test_foo(self):
        foo()
        if not hasattr(sys.stdout, "getvalue"):
            self.fail("need to run in buffered mode")
        output = sys.stdout.getvalue().strip() # because stdout is an StringIO instance
        self.assertEquals(output,'hello world!')

You can set buffer through unit2 command line flag -b, --buffer or in unittest.main options. The opposite is achieved through nosetest flag --nocapture.

if __name__=="__main__":   
    assert not hasattr(sys.stdout, "getvalue")
    unittest.main(module=__name__, buffer=True, exit=False)
    #.
    #----------------------------------------------------------------------
    #Ran 1 test in 0.000s
    #
    #OK
    assert not hasattr(sys.stdout, "getvalue")

    unittest.main(module=__name__, buffer=False)
    #hello world!
    #F
    #======================================================================
    #FAIL: test_foo (__main__.Case)
    #----------------------------------------------------------------------
    #Traceback (most recent call last):
    #  File "test_stdout.py", line 15, in test_foo
    #    self.fail("need to run in buffered mode")
    #AssertionError: need to run in buffered mode
    #
    #----------------------------------------------------------------------
    #Ran 1 test in 0.002s
    #
    #FAILED (failures=1)
FabienAndre
  • 4,514
  • 25
  • 38
  • Note that this interacts with [`--nocapture`](https://stackoverflow.com/questions/5975194/nosetests-is-capturing-the-output-of-my-print-statements-how-to-circumvent-this); in particular, if this flag is set, buffered mode will be disabled. So you have the option of either being able to see the output on the terminal, *or* being able to test that the output is as expected. – ijoseph Sep 22 '17 at 18:51
  • 1
    Is it possible to turn this on and off for each test, because this makes debugging very difficult when using something like ipdb.set_trace() ? – Lqueryvg Nov 05 '17 at 11:32
46

A lot of these answers failed for me because you can't from StringIO import StringIO in Python 3. Here's a minimum working snippet based on @naxa's comment and the Python Cookbook.

from io import StringIO
from unittest.mock import patch

with patch('sys.stdout', new=StringIO()) as fakeOutput:
    print('hello world')
    self.assertEqual(fakeOutput.getvalue().strip(), 'hello world')
Noumenon
  • 5,099
  • 4
  • 53
  • 73
31

In python 3.5 you can use contextlib.redirect_stdout() and StringIO(). Here's the modification to your code

import contextlib
from io import StringIO
from foomodule import foo

def test_foo():
    temp_stdout = StringIO()
    with contextlib.redirect_stdout(temp_stdout):
        foo()
    output = temp_stdout.getvalue().strip()
    assert output == 'hello world!'
Mudit Jain
  • 4,163
  • 1
  • 22
  • 18
  • Great answer! According to the documentation this was added in Python 3.4. – Hypercube Feb 04 '19 at 05:13
  • It's 3.4 for redirect_stdout and 3.5 for redirect_stderr. maybe that's where the confusion arose! – rbennell Jun 07 '19 at 10:58
  • `redirect_stdout()` and `redirect_stderr()` return their input argument. So, `with contextlib.redirect_stdout(StringIO()) as temp_stdout:` gives you all in one line. Tested with 3.7.1. – Adrian W Apr 08 '20 at 11:36
17

I'm only just learning Python and found myself struggling with a similar problem to the one above with unit tests for methods with output. My passing unit test for foo module above has ended up looking like this:

import sys
import unittest
from foo import foo
from StringIO import StringIO

class FooTest (unittest.TestCase):
    def setUp(self):
        self.held, sys.stdout = sys.stdout, StringIO()

    def test_foo(self):
        foo()
        self.assertEqual(sys.stdout.getvalue(),'hello world!\n')
sshow
  • 8,820
  • 4
  • 51
  • 82
sean_robbins
  • 684
  • 6
  • 17
10

Writing tests often shows us a better way to write our code. Similar to Shane's answer, I'd like to suggest yet another way of looking at this. Do you really want to assert that your program outputted a certain string, or just that it constructed a certain string for output? This becomes easier to test, since we can probably assume that the Python print statement does its job correctly.

def foo_msg():
    return 'hello world'

def foo():
    print foo_msg()

Then your test is very simple:

def test_foo_msg():
    assert 'hello world' == foo_msg()

Of course, if you really have a need to test your program's actual output, then feel free to disregard. :)

Alison R.
  • 4,204
  • 28
  • 33
  • 1
    but in this case foo will not be tested... maybe that's a problem – Pedro Valencia Nov 21 '10 at 04:54
  • 5
    From a testing purist's perspective, perhaps it's a problem. From a practical standpoint, if `foo()` doesn't do anything but call the print statement, it's *probably not* a problem. – Alison R. Nov 21 '10 at 17:44
6

Both n611x007 and Noumenon already suggested using unittest.mock, but this answer adapts Acumenus's to show how you can easily wrap unittest.TestCase methods to interact with a mocked stdout.

import io
import unittest
import unittest.mock

msg = "Hello World!"


# function we will be testing
def foo():
    print(msg, end="")


# create a decorator which wraps a TestCase method and pass it a mocked
# stdout object
mock_stdout = unittest.mock.patch('sys.stdout', new_callable=io.StringIO)


class MyTests(unittest.TestCase):

    @mock_stdout
    def test_foo(self, stdout):
        # run the function whose output we want to test
        foo()
        # get its output from the mocked stdout
        actual = stdout.getvalue()
        expected = msg
        self.assertEqual(actual, expected)
rovyko
  • 4,068
  • 5
  • 32
  • 44
5

Based on Rob Kennedy's answer, I wrote a class-based version of the context manager to buffer the output.

Usage is like:

with OutputBuffer() as bf:
    print('hello world')
assert bf.out == 'hello world\n'

Here's the implementation:

from io import StringIO
import sys


class OutputBuffer(object):

    def __init__(self):
        self.stdout = StringIO()
        self.stderr = StringIO()

    def __enter__(self):
        self.original_stdout, self.original_stderr = sys.stdout, sys.stderr
        sys.stdout, sys.stderr = self.stdout, self.stderr
        return self

    def __exit__(self, exception_type, exception, traceback):
        sys.stdout, sys.stderr = self.original_stdout, self.original_stderr

    @property
    def out(self):
        return self.stdout.getvalue()

    @property
    def err(self):
        return self.stderr.getvalue()
Hugo Mota
  • 11,200
  • 9
  • 42
  • 60
2

Or consider using pytest, it has built-in support for asserting stdout and stderr. See docs

def test_myoutput(capsys): # or use "capfd" for fd-level
    print("hello")
    captured = capsys.readouterr()
    assert captured.out == "hello\n"
    print("next")
    captured = capsys.readouterr()
    assert captured.out == "next\n"
pandorabob
  • 93
  • 4
Michel Samia
  • 4,273
  • 2
  • 24
  • 24
1

Unittest ships with a context manager now (Python 3.7, but maybe earlier versions as well). You can just do this:

# example.py

import logging

def method_with_logging():
    logging.info("Hello, World!")

Then in your unit test:

# test.py

from unittest import TestCase
from example import method_with_logging

class TestExample(TestCase):
    def test_logging(self):
        with self.assertLogs() as captured:
            method_with_logging()
        self.assertEqual(len(captured.records), 1) # check that there is only one log message
        self.assertEqual(captured.records[0].getMessage(), "Hello, World!") # and it is the proper one

Taken from https://pythonin1minute.com/how-to-test-logging-in-python/

lancepants
  • 1,462
  • 1
  • 9
  • 8
0

Building on all the awesome answers in this thread, this is how I solved it. I wanted to keep it as stock as possible. I augmented the unit test mechanism using setUp() to capture sys.stdout and sys.stderr, added new assert APIs to check the captured values against an expected value and then restore sys.stdout and sys.stderr upon tearDown(). I did this to keep a similar unit test API as the built-inunittestAPI while still being able to unit test values printed tosys.stdoutorsys.stderr`.

import io
import sys
import unittest


class TestStdout(unittest.TestCase):

    # before each test, capture the sys.stdout and sys.stderr
    def setUp(self):
        self.test_out = io.StringIO()
        self.test_err = io.StringIO()
        self.original_output = sys.stdout
        self.original_err = sys.stderr
        sys.stdout = self.test_out
        sys.stderr = self.test_err

    # restore sys.stdout and sys.stderr after each test
    def tearDown(self):
        sys.stdout = self.original_output
        sys.stderr = self.original_err

    # assert that sys.stdout would be equal to expected value
    def assertStdoutEquals(self, value):
        self.assertEqual(self.test_out.getvalue().strip(), value)

    # assert that sys.stdout would not be equal to expected value
    def assertStdoutNotEquals(self, value):
        self.assertNotEqual(self.test_out.getvalue().strip(), value)

    # assert that sys.stderr would be equal to expected value
    def assertStderrEquals(self, value):
        self.assertEqual(self.test_err.getvalue().strip(), value)

    # assert that sys.stderr would not be equal to expected value
    def assertStderrNotEquals(self, value):
        self.assertNotEqual(self.test_err.getvalue().strip(), value)

    # example of unit test that can capture the printed output
    def test_print_good(self):
        print("------")

        # use assertStdoutEquals(value) to test if your
        # printed value matches your expected `value`
        self.assertStdoutEquals("------")

    # fails the test, expected different from actual!
    def test_print_bad(self):
        print("@=@=")
        self.assertStdoutEquals("@-@-")


if __name__ == '__main__':
    unittest.main()

When the unit test is run, the output is:

$ python3 -m unittest -v tests/print_test.py
test_print_bad (tests.print_test.TestStdout) ... FAIL
test_print_good (tests.print_test.TestStdout) ... ok

======================================================================
FAIL: test_print_bad (tests.print_test.TestStdout)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/tests/print_test.py", line 51, in test_print_bad
    self.assertStdoutEquals("@-@-")
  File "/tests/print_test.py", line 24, in assertStdoutEquals
    self.assertEqual(self.test_out.getvalue().strip(), value)
AssertionError: '@=@=' != '@-@-'
- @=@=
+ @-@-


----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
sorens
  • 4,975
  • 3
  • 29
  • 52
0

I like sorens' straightforward [Answer][1] to the question and sample code, particularly since I'm not familiar with newer features like patch/mock. sorens didn't suggest a way to make the custom assertion methods of the example code's TestStdIO class reusable without resorting to cut/paste, so I took the approach of making TestStdIO a "mixin" class defined in its own module (teststdoutmethods.py in the following example). Since the usual unittest.TestCase-provided assert method references used in TestStdIO will also be available in the test case class, I removed the import unittest line from his sample code and also the derivation of TestStdIO from unittest.TestCase in the class declaration, i.e.,

import io
import sys

class TestStdIO(object):
    def setUp(self):
        ...

Otherwise the code of TestStdIO is as sorens' version sans the two example usages at the end. I used this mixin class version of TestStdIO in some simple unittest test cases of a class in one of the basic example text games in Ch. 2 of Kinsley and McGugan's Beginning Python Game Programming with PyGame, e.g.

import unittest
from teststdoutmethods import TestStdIO   # sorens' TestStdIO as a mixin.
from tank import Tank  # From Beginning Python Game Programming with PyGame.

class Test_Tank_fire(TestStdIO, unittest.TestCase):   # Note multiple inheritance.

    def test_Tank_fire_wAmmo(self):
        oTank1 = Tank('Bill', 5, 100)
        oTank2 = Tank('Jim', 5, 100)

        self.setUp()
        oTank1.fire_at(oTank2)

        self.assertStdoutEquals("Bill fires on Jim\nJim is hit!")
        self.assertEqual(str(oTank1), 'Bill (100 Armor, 4 Ammo)', 'fire_at shooter attribute results incorrect')
        self.assertTrue(str(oTank2) == 'Jim (80 Armor, 5 Ammo)', 'fire_at target attribute results incorrect')

        self.tearDown()

    def test_Tank_fire_woAmmo(self):
        oTank1 = Tank('Bill', 5, 100)
        oTank2 = Tank('Jim', 5, 100)

        # Use up 5 allotted shots.
        for n in range(5):
            oTank1.fire_at(oTank2)

        self.setUp()
        # Try one more.
        oTank1.fire_at(oTank2)

        self.assertStdoutEquals("Bill has no shells!")

        self.tearDown()
    
    def test_Tank_explode(self):
        oTank1 = Tank('Bill', 5, 100)
        oTank2 = Tank('Jim', 5, 100)

        # Use up 4 shots.
        for n in range(4):
            oTank1.fire_at(oTank2)

        self.setUp()
        # Fifth shot should finish the target.
        oTank1.fire_at(oTank2)

        self.assertStdoutEquals("Bill fires on Jim\nJim is hit!\nJim explodes!")
        self.tearDown()

        self.assertTrue(str(oTank2) == 'Jim (DEAD)', 'fire_at target __str__ incorrect when Dead')

The test cases (both successes and ginned failures) worked in Python 3.7. Note that sorens' technique captures all of the stdout output between the setup() and teardown() calls, so I placed these around the specific actions that would generate the specific output I wanted to check. I presume my mixin approach is what sorens would have intended for general reuse, but I'd like to know if anyone has a different recommendation. Thx. [1]: https://stackoverflow.com/a/62429695/7386731

Ambergris
  • 1
  • 2