How to assert output with nosetest/unittest in python?
Solution 1
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.
Solution 2
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!'
Solution 3
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)
Solution 4
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')
Solution 5
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!'
Pedro Valencia
Updated on May 13, 2021Comments
-
Pedro Valencia about 3 years
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?
-
Pedro Valencia over 13 yearsbut in this case foo will not be tested... maybe that's a problem
-
Alison R. over 13 yearsFrom 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. -
Bryan P about 11 yearsNote: Under python 3.x the
StringIO
class must now be imported from theio
module.from io import StringIO
works in python 2.6+. -
Silviu over 10 yearsYou might want to do a
sys.stdout.getvalue().strip()
and not cheat comparing with\n
:) -
matiasg about 10 yearsIf you use
from io import StringIO
in python 2, you get aTypeError: unicode argument expected, got 'str'
when printing. -
Andy Hayden over 9 yearsThis has worked really well for me in 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 over 9 yearsHmmm it may be that in python 2 we want
from io import BytesIO as StringIO
and in python 3 justfrom io import StringIO
. Seemed to fix the issue in my tests I think. -
Andy Hayden over 9 yearsOoop, 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!
-
Lucretiel over 9 yearsQuick note: In python 3.4, you can use the contextlib.redirect_stdout context manager to do this in a way that is exception safe:
with redirect_stdout(out):
-
ThorSummoner over 8 yearsYou don't need to do
saved_stdout = sys.stdout
, you always have a magic ref to this atsys.__stdout__
, eg, you only needsys.stdout = sys.__stdout__
in your cleanup. -
Seng Cheong over 8 years@ThorSummoner Thanks, this just simplified some of my tests... for scuba which I see you've starred....small world!
-
KobeJohn about 8 yearsGood one. Can you include a minimal example since links can disappear and content can change?
-
Palimondo about 7 yearsWhy are all the examples here calling
strip()
on theunicode
returned fromStringIO.getvalue()
? -
Rob Kennedy about 7 years@Palimondo, it removes the trailing line break.
-
Palimondo about 7 years@RobKennedy But it also removes newlines that are at the beginning and those at the end and are legitimate part of the output, right?
-
Rob Kennedy about 7 yearsThey're evidently not, in this case, @Palimondo. Note that the test in the question did the same thing.
-
Vedran Šego about 7 yearsAny way to make this work with
from sys import stderr
, instead of just withimport sys
? -
Rob Kennedy about 7 yearsNo, @Vedran. This relies on rebinding the name that belongs to
sys
. With your import statement, you're creating a local variable namedstderr
that received a copy of the value insys.stderr
. Changes to one are not reflected in the other. -
Vedran Šego about 7 years@RobKennedy I understand that. My question was: is there a way to capture
stderr
in that case too? -
Rob Kennedy about 7 yearsSounds like a good question to ask on Stack Overflow, @Vedran.
-
ijoseph almost 7 yearsNote that this interacts with
--nocapture
; 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. -
Lqueryvg over 6 yearsIs it possible to turn this on and off for each test, because this makes debugging very difficult when using something like ipdb.set_trace() ?
-
Sylhare almost 6 yearsI love this one for Python 3, it is clean!
-
Hypercube over 5 yearsGreat answer! According to the documentation this was added in Python 3.4.
-
Gulzar about 5 yearsHow do I flush the out buffer?
out.flush()
seems to do nothing (python 2.7) -
Rob Kennedy about 5 years@Gulzar,
StringIO
has no buffer to flush. It simply is the buffer. If code has runprint 'foo'
while the above context manager was active, thenfoo
will be inout
. -
Hubert Grzeskowiak about 5 yearsI 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?
-
Rob Kennedy about 5 yearsFrom that meager description, I couldn't possibly know what you're doing wrong, @Hubert. Have you considered posting a question on Stack Overflow?
-
rbennell about 5 yearsIt's 3.4 for redirect_stdout and 3.5 for redirect_stderr. maybe that's where the confusion arose!
-
Edwarric over 4 yearsThe StringIO module is deprecated. Instead
from io import StringIO
-
Justin Eyster over 4 yearsThis was the only solution on this page that worked for me! Thank you.
-
Adrian W about 4 years
redirect_stdout()
andredirect_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. -
MROB about 4 yearsIt is worth mentioning that the reason for reassigning
sys.stdout
is the fact that it is write-only (its mode isw
), so it is unreadable. -
StressedBoi69420 over 2 years
ModuleNotFoundError: No module named 'StringIO'
. Python 3:from io import StringIO
-
MolbOrg over 2 yearssweet, works like charm in 3.7