如何在 python unitest 中模拟 httpx 的 post() 和 get() 调用?

问题描述 投票:0回答:1

当我修补整个功能

get_post()
get_call()
时,以下测试有效。我如何修补
httpx.post()
httpx.get()

src/app.py

import httpx

class Client:
    def __init__(self, url):
        self.url = url

    def post_call(self, payload):
        response = httpx.post(
                url=self.url,
                json=payload,
            )
            response.raise_for_status()
            return response.json()

    def get_call(self, id):
        response = httpx.get(
                url=self.url,
                params={"id": id},
            )

        response.raise_for_status()
        return response.json()

test/test.py

from unittest.mock import patch

import httpx
import pytest
from src import app


@pytest.mark.anyio
@patch("app.get_call",return_value=httpx.Response(200, json ={"status":"passed"}))
def test_get(mocker):
    cl = Client("test-url")
    result = cl.get_call("test")
    assert result.json() == {"status":"passed"}

@pytest.mark.anyio
@patch("app.get_post", return_value=httpx.Response(200,json={"status":"passed"}),)
def test_post(mocker):
    cl = Client("test-url")
    result = cl.post_call({"data":"test"})
    assert result.json() == {"status":"passed"}

当我尝试修补 httpx 调用时,我被抛出错误:

ModuleNotFoundError: No module named 'app.Client'; 'app' is not a package

python unit-testing pytest python-unittest python-unittest.mock
1个回答
1
投票

修复导入路径

假设您的目录布局如下所示:

.
├── src
│   ├── app.py
└── test
    └── test_app.py

那你不想要

from src import app
,因为
src
不是一个包。它只是一个目录。为了帮助 pytest 找到
app.py
,创建一个空的
src/conftest.py
,这样你就有:

.
├── src
│   ├── app.py
│   └── conftest.py
└── test
    └── test_app.py

然后在

test_app.py
中,而不是:

from src import app

写:

import app

修复语法错误

你的测试代码中少了一个

"
;如所写,尝试运行测试将失败:

E     File "/home/lars/tmp/python/test/test_app.py", line 19
E       result = cl.post_call({"data:"test"})
E                                         ^
E   SyntaxError: unterminated string literal (detected at line 19)

我们需要将第19行更正为:

result = cl.post_call({"data": "test"})

另外,在你的测试函数中你有:

cl = Client("test")

这会失败,因为你有

import app
,而不是
from app import Client
。我们需要修复
import
语句或修复测试代码:

cl = app.Client("test")

修复
@patch
来电

您现有的

patch
调用被破坏了,因为您的
app
模块既没有
get_call
也没有
get_post
方法——这些是 Client 类上的方法。幸运的是,我们需要通过修补
httpx.get
httpx.post
来替换它:

from unittest.mock import patch

import httpx
import pytest
import app


@patch("app.httpx.get")
def test_get(fake_httpx_get):
    fake_httpx_get.return_value = httpx.Response(
        200,
        json={"status": "passed"},
        request=httpx.Request("GET", "test"),
    )
    cl = app.Client("test-url")
    result = cl.get_call("test")
    assert result == {"status": "passed"}


@patch("app.httpx.post")
def test_post(fake_httpx_post):
    fake_httpx_post.return_value = httpx.Response(
        200,
        json={"status": "passed"},
        request=httpx.Request("POST", "test"),
    )
    cl = app.Client("test-url")
    result = cl.post_call({"data": "test"})
    assert result == {"status": "passed"}

我已经移动了在函数内部的 mock 上设置返回值,因为我们还需要设置

request
参数;否则,您对
res.raise_for_status()
的调用将失败:

RuntimeError: Cannot call `raise_for_status` as the request instance has not been set on this response.

有了上面的目录结构和测试代码,当我们从顶级目录运行

pytest
时,结果是:

========================================================= test session starts =========================================================
platform linux -- Python 3.11.1, pytest-7.2.1, pluggy-1.0.0 -- /home/lars/.local/share/virtualenvs/python-LD_ZK5QN/bin/python
cachedir: .pytest_cache
rootdir: /home/lars/tmp/python
plugins: anyio-3.6.2
collected 2 items

test/test_app.py::test_get PASSED                                                                                               [ 50%]
test/test_app.py::test_post PASSED                                                                                              [100%]

========================================================== 2 passed in 0.08s ==========================================================

我们还可以测试您的代码如何响应 HTTP 错误:

@patch("app.httpx.post")
def test_post_failure(fake_httpx_post):
    fake_httpx_post.return_value = httpx.Response(
        400,
        json={"status": "failed"},
        request=httpx.Request("POST", "test"),
    )
    cl = app.Client("test-url")
    with pytest.raises(httpx.HTTPStatusError):
        cl.post_call({"data": "test"})
© www.soinside.com 2019 - 2024. All rights reserved.