sorin
sorin

Reputation: 170698

How to access the py.test capsys from inside a test?

py.test documentations says that I should add capsys parameter to my test methods but in my case this doesn't seem to be possible.

class testAll(unittest.TestCase):

    def setUp(self):
        self.cwd = os.path.abspath(os.path.split(inspect.getfile(inspect.currentframe()))[0])
        os.chdir(self.cwd)

    def execute(self, cmd, result=0):
        """
        Helper method used by many other tests, that would prevent replicating too much code.
        """
        # cmd = "%s > /dev/null 2>&1" % cmd
        ret = os.system(cmd) >> 8
        self.assertEqual(ret, result, "`%s` returned %s instead of %s (cws=%s)\n\t%s" % (cmd, ret, result, os.getcwd(), OUTPUT)) ### << how to access the output from here

    def test_1(self):
        self.execute("do someting", 0) 

Upvotes: 10

Views: 3154

Answers (3)

kernelplv
kernelplv

Reputation: 191

# conftest.py
class TTY:
   def communicate(self):
      with self.trace():
         print('wow!')

@pytest.fixture(autouse=True)
def set_capsys(capsys):
    TTY.trace = capsys.disabled

@pytest.fixture
def get_tty():

   _get_tty():
      return TTY()

   return _get_tty


# test_wow.py
def test_wow(get_tty):
   get_tty().communicate()

Upvotes: 0

Amiga500
Amiga500

Reputation: 1275

Thomas Wright's answer is perfect. I'm just sticking this code block here for my own reference as my search led me here and I'll likely forget this in future! [doing a few things in this so useful reference for me]. If anyone is looking and sees where it can be improved - suggest away!

import os
import pytest

from _pytest.monkeypatch import MonkeyPatch
from unittest import TestCase

# -----------------------------------------------------------------------------
def foo_under_test(inp1):
    """Example of a Method under test"""
    do_some_calcs_here = inp1*2
    get_a_return = ClassCalled.foo_called(do_some_calcs_here)
    return get_a_return


# -----------------------------------------------------------------------------
class ClassUnderTest():
    """Example of a Class contained Method under test"""

    def __init__(self):
        """Instantiate the class"""
        self.var1 = "TestVar"

    def foo_under_test2(self, inp11):
        """The class method under test"""
        return self.var1 + self.foo_called2(inp11)

    def foo_called2(self, inp12):
        """Nominal sub-foo to foo_under_test2"""
        return str(inp12*5)


# -----------------------------------------------------------------------------
class ClassCalled:
    """Example of a class that could be called by foo_under_test"""

    def foo_called(inp2):
        """Sub-foo to foo_under_test"""
        return inp2 * 2


# -----------------------------------------------------------------------------
class MockResponses:
    """Class for holding the mock responses"""

    def foo_called(inp2):
        """**Mock of foo_called**"""
        return inp2*3

    def foo_called2(inp12):
        """**Mock of foo_called**"""
        return str(inp12*4)


# -----------------------------------------------------------------------------
class Test_foo_under_test(TestCase):
    """Test class - means of grouping up tests for a target function

    This one is addressing the individual function (not within a class)
    """

    # ---------------------------------------------------------------
    @pytest.fixture(autouse=True)
    def capsys(self, capsys):
        """Capsys hook into this class"""
        self.capsys = capsys

    def print_to_console(self, strOut):
        """Print strOut to console (even within a pyTest execution)"""
        with self.capsys.disabled():
            print(strOut)

    def setUp(self):
        """Ran by pyTest before running any test_*() functions"""
        self.monkeypatch = MonkeyPatch()

    # ---------------------------------------------------------------

    def test_1(self):
        """**Test case**"""
        def mock_foo_called(inp2):
            return MockResponses.foo_called(inp2)

        mockedFoo = ClassCalled.foo_called  # Need to get this handle here
        self.monkeypatch.setattr(ClassCalled, "foo_called", mock_foo_called)

        x = foo_under_test(1)
        self.print_to_console("\n")
        strOut = "Rtn from foo: " + str(x)
        self.print_to_console(strOut)

        assert x == 6

        # Manually clear the monkey patch
        self.monkeypatch.setattr(
            ClassCalled, "foo_called", mockedFoo)
        """I've noticed with me having monkeypatch inside the
        class, the damn thing persists across functions. 
        This is the only workaround I've found so far"""


# -----------------------------------------------------------------------------
class Test_ClassUnderTest_foo_under_test(TestCase):
    """Test class - means of grouping up tests for a target function

    This one is addressing the function within a class
    """

    # ---------------------------------------------------------------
    @pytest.fixture(autouse=True)
    def capsys(self, capsys):
        """Capsys hook into this class"""
        self.capsys = capsys

    def print_to_console(self, strOut):
        """Print strOut to console (even within a pyTest execution)"""
        with self.capsys.disabled():
            print(strOut)

    def setUp(self):
        """Ran by pyTest before running any test_*() functions"""
        self.monkeypatch = MonkeyPatch()

    # ---------------------------------------------------------------

    def test_1(self):
        """**Test case**"""

        def mock_foo_called2(self, inp2):
            """
            Mock function

            Defining a mock function, note this can be dealt with directly
            here, or if its more comprehensible, put it in a separate class
            (i.e. MockResponses)
            """
            # return MockResponses.foo_called2(inp2)  # Delegated approach 
            return str(inp2*4)  # Direct approach

        """Note that the existence of self within this test class forces
        a wrapper around calling a MockClass - so we have to go through
        both the line below and the function mock_foo_called2() above to 
        properly invoke MockResponses.foo_called2()
        """
        mockedFoo = ClassUnderTest.foo_called2
        self.monkeypatch.setattr(
            ClassUnderTest, "foo_called2", mock_foo_called2)

        x = ClassUnderTest().foo_under_test2(1)
        strOut = "Rtn from foo: " + str(x)
        self.print_to_console("\n")
        self.print_to_console(strOut)

        assert x == "TestVar" + str(4)
        self.monkeypatch.setattr(
            ClassUnderTest, "foo_called2", mockedFoo)




# -----------------------------------------------------------------------------
# ---- Main
if __name__ == "__main__":
    #
    # Setup for pytest
    outFileName = os.path.basename(__file__)[:-3]  # Remove the .py from end
    currScript = os.path.basename(__file__)

    # -------------------------------------------------------------------------
    # PyTest execution
    pytest.main([currScript, "--html", outFileName + "_report.html"])

    rtnA = foo_under_test(1)
    print(rtnA == 4)
    # This should output 4, demonstrating effect of stub (which produced 6)

    rtnB = ClassUnderTest().foo_under_test2(1)
    print(rtnB == "TestVar"+str(5))
    # This should output "TestVar5", demonstrating effect of stub

Upvotes: 1

Thomas Wright
Thomas Wright

Reputation: 134

You could define a helper function in the class that inherits the capsys fixture:

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

Then call this function inside the test:

out,err = self.capsys.readouterr()

assert out == 'foobar'

Kudos to Michał Krassowski for his workaround which helped me work through a similar problem.

https://github.com/pytest-dev/pytest/issues/2504#issuecomment-309475790

Upvotes: 6

Related Questions