是否可以在另一个夹具中使用夹具并在测试中使用?

时间:2017-10-04 14:58:09

标签: python mocking pytest

我使用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: ...。我不太确定这里适当的模式是什么。

2 个答案:

答案 0 :(得分: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 fixture。

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

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

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

如果进行此更改,在测试中使用api夹具也意味着在mocked_requests的上下文中执行它,即使您未在测试中直接指定后者函数的论点。它仍然可以明确地使用它,例如,如果你想在返回的模拟上做出断言。

答案 1 :(得分:0)

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

您的代码如下:

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