简体   繁体   中英

Capture stderr/stdout in pytest within a fixture

I am having trouble capturing the stderr and stdout for pytest when I am using a fixture. It works okay if the test is just directly called. The reason I want it in a fixture is because I need to call it multiple times based on a file that I will be reading.

Here's a working example, lets say this content is in ' test_something.py ':

import sys
import pytest


def printing():
    print("testing only")
    print("erroring", file=sys.stderr)
    sys.exit(1)

def test_print(capsys):
    with pytest.raises(SystemExit) as excinfo:
        printing()

    stdout, errout = capsys.readouterr()
    assert stdout == "testing only\n"
    assert errout == "erroring\n"
    assert excinfo.type == SystemExit


class TestPrint():
    @pytest.fixture(name="getResponse", scope="class", params=[0])
    def getResponse(self, request):
        with pytest.raises(SystemExit) as excinfo:
            printing()
            return excinfo

    def test_print_class(self, getResponse, capsys):
        excinfo = getResponse
        stdout, errout = capsys.readouterr()
        assert stdout == "testing only\n"
        assert errout == "erroring\n"
        assert excinfo.type == SystemExit

if you run 'pytest -v' for the above, you get:

tests/test_something.py::test_print PASSED                                                                                                                                           [ 50%]
tests/test_something.py::TestPrint::test_print_class[0] FAILED                                                                                                                       [100%]

The 'test_print_class' reads empty stdout and stderr, why is that? How can I make it work? Thanks for the help!

The main problem is that you cannot easily mix function and class scope fixtures. A function scope fixture would work:

class TestPrint:
    @pytest.fixture(name="getResponse")
    def getResponse(self):
        with pytest.raises(SystemExit):
            printing()

    def test_print_class(self, capsys, getResponse):
        stdout, errout = capsys.readouterr()
        assert stdout == "testing only\n"
        assert errout == "erroring\n"

The class scoped fixture is run before the function scoped capsys is invoked, so it will not the catch stdout/stderr from the fixture.
The problem is also shown if you try to add capsys to the fixture itself. In this case pytest will complain:

ScopeMismatch: You tried to access the 'function' scoped fixture 'capsys' with a 'class' scoped request object, involved factories

Note that I also removed the return excinfo part, as this will never be reached - the context manager scope is left as soon as the exception occurs. The check is also unneeded, because it is already done in the fixture.

So the answer is: it is not possible to achieve what you want this way. Fixtures are there to provide setup/teardown and data for the test, not part of the test itself.
I'm not sure about your actual use case, depending on that you can

  • use a function-scoped fixture (if you want to call printing for every test, which is what I understood from your question)
  • write a separate test for the printing exception handling (as you do in the first test), and just use it in the fixture to setup it once for all other tests, if that is what you need

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM