当我修补整个功能
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
假设您的目录布局如下所示:
.
├── 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"})