2
votes

I have some unittest-based code that currently looks like this:

class TestMain(TestCase):
    def run_prog(self, args):
        with TemporaryFile() as stdout:
            old_stdout = sys.stdout
            try:
                main.main()
                stdout.seek(0)
                stdout_data = stdout.read()
            finally:
                sys.stdout = old_stdout
        return stdout_data

    def test_one(self):
        out = self.run_prog(...)

    def test_two(self):
        out = self.run_prog(...)

    def test_three(self):
        out = self.run_prog(...)

run_prog invokes the "main" program under test and manually captures its stdout.

I'm in the process of converting this project to pytest, but this last piece has pushed the limits of my understanding of pytest fixtures.

I understand that pytest has full support for capturing stdout/stderr and I would like to leverage this.

The problem is, their examples work on a test-function level:

def test_myoutput(capfd):
    do_something
    captured = capsys.readouterr()
    assert captured.out == "hello\n"
    assert captured.err == "world\n"

In my case, run_prog is used 42 times, so I'm trying to use the fixture starting at run_prog -- the calling functions ideally don't need to bother with capsys/capfd.

Is there a way to "invoke" the fixture from my run_prog helper? Or do I need to add capfd to all 42 tests and pass it to run_prog?

2

2 Answers

3
votes

You can define an autouse fixture that will store the CaptureFixture object (returned by the capsys fixture) as an instance property:

class TestMain(TestCase):

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

    def run_prog(self, args):
        main.main()
        return self._capsys.out

    def test_out(self):
        assert self.run_prog('spam') == 'eggs'

The TestMain.inject_capsys fixture will be rerun for each test, guaranteeing the test isolation (no output from test_one will be leaked in test_two etc).

0
votes

Here's a slight variation on hoefling's answer that gives a little more control over the scope of the capsys fixture.

It uses request.getfixturevalue() to retrieve the fixture at function invocation time:

import pytest
import sys

class TestMain:
    @pytest.fixture(autouse=True)
    def inject_request(self, request):
        self.request = request

    def run_prog(self, message):
        capfd = self.request.getfixturevalue('capfd')

        sys.stdout.write(message)

        captured = capfd.readouterr()
        assert captured.out == message

    def test_one(self):
        self.run_prog("Hello world!")