3

I have being trying to find a way to use mocking decorators and pytest capsys at the same time but I wasn't able to find the right way to do it.

import pytest
import requests_mock


@requests_mock.mock()
def test_with_mock(m):
    pass

def test_with_capsys(capsys):
    pass


# how to write a test that works with both?
hoefling
  • 59,418
  • 12
  • 147
  • 194
sorin
  • 161,544
  • 178
  • 535
  • 806

1 Answers1

11

As stated in the request-mock's docs:

pytest has its own method of registering and loading custom fixtures. requests-mock provides an external fixture registered with pytest such that it is usable simply by specifying it as a parameter. There is no need to import requests-mock it simply needs to be installed and specify the argument requests_mock.

The fixture then provides the same interface as the requests_mock.Mocker letting you use requests-mock as you would expect.

>>> import pytest
>>> import requests

>>> def test_url(requests_mock):
...     requests_mock.get('http://test.com', text='data')
...     assert 'data' == requests.get('http://test.com').text
...

So just use the requests_mock fixture instead of the decorator:

def test_with_mock_and_capsys(requests_mock, capsys):
    pass

Background

pytest doesn't play along with function decorators that add positional arguments to the test function. pytest considers all arguments that

  • aren't bound to an instance or type as in instance or class methods;
  • don't have default values;
  • aren't bound with functools.partial;
  • aren't replaced with unittest.mock mocks

to be replaced with fixture values, and will fail if it doesn't find a suitable fixture for any argument. So stuff like

import functools
import pytest


def deco(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args += ('spam',)
        return func(*args, **kwargs)
    return wrapper


@deco
def test_spam(spam_arg):
    assert True

will fail, and this is exactly what requests-mock does. A workaround to that would be passing the mocker via keyword args:

import pytest
import requests_mock


@requests_mock.Mocker(kw='m')
def test_with_mock_and_fixtures(capsys, **kwargs):
    m = kwargs['m']
    ...

but since requests-mock already offers a fixture, why bother using the decorator?

Community
  • 1
  • 1
hoefling
  • 59,418
  • 12
  • 147
  • 194
  • 1
    Hi, I'm the author of requests-mock. Thanks for answering, I'm going to link people to this from now on. Do you mind if i just wholesale copy that background snippet into the documentation? – jamielennox Feb 05 '20 at 02:37
  • 1
    Sure, glad the example was helpful! – hoefling Feb 05 '20 at 07:26