2

I generally like the pytest warnings capture hook, as I can use it to force my test suite to not have any warnings triggered. However, I have one test that requires the warnings to print to stderr correctly to work.

How can I disable the warnings capture for just the one test?

For instance, something like

def test_warning():
    mystderr = StringIO()
    sys.stderr = mystderr
    warnings.warn('warning')
    assert 'UserWarning: warning' in mystderr.getvalue()

(I know I can use capsys, I just want to show the basic idea)

asmeurer
  • 86,894
  • 26
  • 169
  • 240

2 Answers2

1

Thanks to the narrowing down in this discussion, I think the question might better be titled "In pytest, how to capture warnings and their standard error output in a single test?". Given that suggested rewording, I think the answer is "it can't, you need a separate test".

If there were no standard error capture requirement, you should be able to use the @pytest.mark.filterwarnings annotation for this.

@pytest.mark.filterwarnings("ignore")
def test_one():
    assert api_v1() == 1

From: https://docs.pytest.org/en/latest/warnings.html#pytest-mark-filterwarnings

@wim points out in a comment this will not capture the warning, though, and the answer he lays out captures and asserts on the warnings in a standard way.

If there were stderr output but not Python warnings thrown, capsys would be the technique, as you say https://docs.pytest.org/en/latest/capture.html

I don't think it's meaningful to do both in a pytest test, because of the nature of the pytest implementation.

As previously noted pytest redirects stderr etc to an internal recorder. Secondly, it defines its own warnings handler https://github.com/pytest-dev/pytest/blob/master/src/_pytest/warnings.py#L59

It is similar in idea to the answer to this question: https://stackoverflow.com/a/5645133/5729872

I had a little poke around with redefining warnings.showwarning(), which worked fine from vanilla python, but pytest deliberately reinitializes that as well.

won't work in pytest, only straight python -->

def func(x):
    warnings.warn('wwarn')
    print(warnings.showwarning.__doc__)
    # print('ewarn', file=sys.stderr)
    return x + 1

sworig = warnings.showwarning

def showwarning_wrapper(message, category, filename, lineno, file=None, line=None):
    """Local override for showwarning()"""
    print('swwrapper({})'.format(file) )
    sworig(message,category,filename,lineno,file,line)

warnings.showwarning = showwarning_wrapper

<-- won't work in pytest, only straight python

You could probably put a warnings handler in your test case that reoutput to stderr ... but that doesn't prove much about the code under test, at that point.

It is your system at the end of the day. If after consideration of the point made by @wim that testing stderr as such may not prove much, you decide you still need it, I suggest separating the testing of the Python warning object (python caller layer) and the contents of stderr (calling shell layer). The first test would look at Python warning objects only. The new second test case would call the library under test as a script, through popen() or similar, and assert on the resulting standard error and output.

Adam Burke
  • 724
  • 1
  • 7
  • 21
-1

I'll encourage you to think about this problem in a different way.

When you want to assert that some of your code triggers warnings, you should be using a pytest.warns context for that. Check the warning message by using the match keyword, and avoid the extra complications of trying to capture it from stderr.

import re
import warnings

import pytest

def test_warning():
    expected_warning_message = "my warning"
    match = re.escape(expected_warning_message)
    with pytest.warns(UserWarning, match=match):
        warnings.warn("my warning", UserWarning)

That should be the edge of your testing responsibility. It is not your responsibility to test that the warnings module itself prints some output to stderr, because that behavior is coming from standard library code and it should be tested by Python itself.

wim
  • 338,267
  • 99
  • 616
  • 750
  • No, I really do want to test stderr. I have some code that hooks how the warning is printed and I want to verify that it is indeed printed exactly the way it should be. But only for this one test. The rest of the tests in my test suite are unrelated to warnings. – asmeurer Jun 05 '19 at 04:53
  • Sounds fragile (stderr can be redirected or whatever) - you should possibly make your code hook into the warnings filter directly instead of triggering on however the warning is actually rendered. It also sounds like you're trying to do integration testing instead of actually testing your own library code. – wim Jun 05 '19 at 05:04
  • Yes, it's an integration test. The library code is an implementation detail. What I care about, and want to test, is the end result, namely, how the warning is printed. – asmeurer Jun 05 '19 at 05:23
  • I see. Do you insist on using a stderr capture for this? What about using `recwarn` fixture, and then asserting on the `str()` of any recorded warning messages, [as shown in docs](https://docs.pytest.org/en/latest/warnings.html#recwarn)? `pytest.warns` context also accepts a "match" kwarg (regex) to make precise assertions about the contents of the printed message. – wim Jun 05 '19 at 06:11