【问题标题】:Is it possible to use a fixture in another fixture and both in a test?是否可以在另一个夹具中使用夹具并在测试中使用两者?
【发布时间】:2017-10-04 14:58:09
【问题描述】:

我正在使用 unittest.mock 模拟一个 API。我的界面是一个在幕后使用requests 的类。所以我正在做这样的事情:

@pytest.fixture
def mocked_api_and_requests():
    with mock.patch('my.thing.requests') as mock_requests:
        mock_requests.post.return_value = good_credentials
        api = MyApi(some='values', that='are defaults')
        yield api, mock_requests


def test_my_thing_one(mocked_api_and_requests):
    api, mocked_requests = mocked_api_and_requests
    ...  # some assertion or another

def test_my_thing_two(mocked_api_and_requests):
    api, mocked_requests = mocked_api_and_requests
    ... # some other assertions that are different

正如您可能看到的那样,我在这两个测试中的第一行都是相同的,闻起来对我来说还不够干。

我希望能够做类似的事情:

def test_my_thing_one(mock_requests, logged_in_api):
    mock_requests.get.return_value = ...

不必解压这些值,但我不确定是否有办法使用 pytest 可靠地做到这一点。如果它在documentation for fixtures 中,我完全错过了它。但确实感觉应该有一种正确的方法来做我想做的事情。

有什么想法吗?如果我需要走那条路,我愿意使用class TestGivenLoggedInApiAndMockRequests: ...。我只是不太确定这里的合适模式是什么。

【问题讨论】:

  • 这里,logged_in_api 代表在夹具mocked_api_and_requests 中定义的api
  • @ChandaKorat 是的 - 基本上将参数作为单独的参数而不是一个并将它们解包。

标签: python mocking pytest


【解决方案1】:

可以通过使用多个夹具来达到您想要的结果。

注意:我对您的示例进行了最低限度的修改,以便我的答案中的代码是独立的,但您应该能够轻松地将其适应您的用例。

myapi.py

import requests

class MyApi:

    def get_uuid(self):
        return requests.get('http://httpbin.org/uuid').json()['uuid']

test.py

from unittest import mock
import pytest
from myapi import MyApi

FAKE_RESPONSE_PAYLOAD = {
    'uuid': '12e77ecf-8ce7-4076-84d2-508a51b1332a',
}

@pytest.fixture
def mocked_requests():
    with mock.patch('myapi.requests') as _mocked_requests:
        response_mock = mock.Mock()
        response_mock.json.return_value = FAKE_RESPONSE_PAYLOAD
        _mocked_requests.get.return_value = response_mock
        yield _mocked_requests

@pytest.fixture
def api():
    return MyApi()

def test_requests_was_called(mocked_requests, api):
    assert not mocked_requests.get.called
    api.get_uuid()
    assert mocked_requests.get.called

def test_uuid_is_returned(mocked_requests, api):
    uuid = api.get_uuid()
    assert uuid == FAKE_RESPONSE_PAYLOAD['uuid']

def test_actual_api_call(api):  # Notice we don't mock anything here!
    uuid = api.get_uuid()
    assert uuid != FAKE_RESPONSE_PAYLOAD['uuid']

我没有定义一个返回元组的夹具,而是定义了两个夹具,它们可以被测试独立使用。像这样组合夹具的一个优点是它们可以独立使用,例如最后一个测试实际上调用了 API,只是因为没有使用 mock_requests 夹具。

请注意 -- 要直接回答问题标题 -- 您还可以将 mocked_requests 作为 api 固定装置的先决条件,只需将其添加到参数中,如下所示:

@pytest.fixture
def api(mocked_requests):
    return MyApi()

如果您运行测试套件,您将看到它有效,因为test_actual_api_call 将不再通过。

如果您进行此更改,则在测试中使用api 夹具也意味着在mocked_requests 的上下文中执行它,即使您没有在测试函数的参数中直接指定后者。仍然可以显式使用它,例如如果您想对返回的模拟进行断言。

【讨论】:

    【解决方案2】:

    如果您负担不起轻松split your tuple fixture into two independent fixtures,您现在可以使用我的pytest-cases 插件将元组或列表夹具“解包”到其他夹具中,如this answer 中所述。

    您的代码如下所示:

    from pytest_cases import pytest_fixture_plus
    
    @pytest_fixture_plus(unpack_into="api,mocked_requests")
    def mocked_api_and_requests():
        with mock.patch('my.thing.requests') as mock_requests:
            mock_requests.post.return_value = good_credentials
            api = MyApi(some='values', that='are defaults')
            yield api, mock_requests
    
    def test_my_thing_one(api, mocked_requests):
        ...  # some assertion or another
    
    def test_my_thing_two(api, mocked_requests):
        ... # some other assertions that are different
    

    【讨论】:

    • 你确定这个工作吗?使用 pytest_fixture_plus 和 yield 会导致错误 #get the required fixture's value (the tuple to unpack) source_fixture_value = kwargs.pop(source_f_name) # unpack: get the item at the right position。 > return source_fixture_value[_value_idx] E TypeError: 'generator' object is not subscriptable
    • 它应该可以工作,是的。让我们讨论一下您打开的问题github.com/smarie/python-pytest-cases/issues/130
    猜你喜欢
    • 1970-01-01
    • 2013-04-01
    • 2012-04-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-11-19
    • 2018-11-22
    • 2020-02-17
    相关资源
    最近更新 更多