英文:
How to mock the post() and get() calls of httpx in python unitest?
问题
以下是您要翻译的部分:
The following test works when I patch the entire function get_post()
and get_call()
. How can I patch the httpx.post()
and httpx.get()
?
In 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()
In 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"}
When I tried to patch the httpx call then I get thrown with the error:
ModuleNotFoundError: No module named 'app.Client'; 'app' is not a package
英文:
The following test works when I patch the entire function get_post()
and get_call()
. How can I patch the httpx.post()
and httpx.get()
?
In 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()
In 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"}
When I tried to patch the httpx call then I get thrown with the error:
ModuleNotFoundError: No module named 'app.Client'; 'app' is not a package
答案1
得分: 2
修复导入路径
假设您的目录结构如下:
.
├── 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")
这将失败,因为您导入了 app
,而不是 from app import Client
。我们需要修复导入语句或修复测试代码:
cl = app.Client("test")
修复 @patch
调用
您现有的 patch
调用是有问题的,因为您的 app
模块既没有 get_call
方法也没有 post_call
方法 — 这些方法在 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"}
我将设置模拟返回值的代码移到函数内部,因为我们还需要设置 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"})
英文:
Fix import paths
Assuming that your directory layout looks like this:
.
├── src
│   ├── app.py
└── test
└── test_app.py
Then you don't want from src import app
, because src
isn't a package. It's just a directory. To help pytest locate app.py
, create an empty src/conftest.py
so that you have:
.
├── src
│   ├── app.py
│   └── conftest.py
└── test
└── test_app.py
Then in test_app.py
, instead of:
from src import app
Write:
import app
Fix syntax errors
There is a missing "
in your test code; as written, attempting to run a test will fail with:
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)
We need to correct line 19 to read:
result = cl.post_call({"data": "test"})
Additionally, in your test functions you have:
cl = Client("test")
This will fail because you have import app
, not from app import Client
. We need to either fix the import
statement or fix the test code:
cl = app.Client("test")
Fix @patch
calls
Your existing patch
invocations are broken as written, since your app
module has neither get_call
or get_post
methods -- these are methods on the Client class. Fortunately, we need to replace this by patching httpx.get
and 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"}
I've moved setting the return value on the mock inside the functions because we also need to set the request
parameter; without that your calls to res.raise_for_status()
will fail:
RuntimeError: Cannot call `raise_for_status` as the request instance has not been set on this response.
With the above directory structure and test code, when we run pytest
from the top-level directory, it results in:
========================================================= 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 ==========================================================
We can also test how your code responds to HTTP errors:
@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"})
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论