[英]Testing server-sent events in Flask with pytest
I have a Flask application where I use server-sent events to send data to my front-end.我有一个 Flask 应用程序,我使用服务器发送的事件将数据发送到我的前端。
@bp.route("/stream", methods=("GET",))
def stream_translations():
translation_schema = TranslationSchema()
def event_stream():
while True:
recently_updated = [
translation_schema.dump(translation)
for translation in recently_updated_translations()]
if recently_updated:
yield f"data: {json.dumps(recently_updated)}\n\n"
return Response(event_stream(), mimetype="text/event-stream")
It works fine, but I also want to write a test for it to make sure.它工作正常,但我也想为它写一个测试以确保。 I've never written a test for a generator before, and definitely not server-sent events.我以前从未为生成器编写过测试,绝对不是服务器发送的事件。 Currently this is what I have:目前这就是我所拥有的:
def test_stream(client):
response = client.get("/translations/stream")
assert response.status_code == 200
assert response.mimetype == "text/event-stream"
Of course this just tests the response, but I also want to test the event_stream()
generator.当然这只是测试响应,但我也想测试event_stream()
生成器。 How do I do this?我该怎么做呢?
With some minor refactoring, we can successfully test our SSE endpoints through careful test mocks.通过一些小的重构,我们可以通过仔细的测试模拟成功地测试我们的 SSE 端点。
The first issue that you may be facing when testing this code is that SSE event streams promotes using while True
loops.测试此代码时您可能面临的第一个问题是 SSE 事件流促进使用while True
循环。 This makes sense in a browser environment, but not so much in a server-side integration test, since it will cause your test cases to "hang".这在浏览器环境中是有意义的,但在服务器端集成测试中则没有意义,因为它会导致您的测试用例“挂起”。
We can address this by refactoring your generator code into a helper function:我们可以通过将生成器代码重构为帮助程序 function 来解决此问题:
import time
def event_stream(timeout = 0.0):
starting_time = time.time()
while not timeout or (time.time() - starting_time < timeout):
...
yield f'data:{json.dumps(...)}'
With this, you can refactor your original stream_translations
function as such:有了这个,你可以重构你原来的stream_translations
function:
@bp.route("/stream", methods=("GET",))
def stream_translations():
return Response(event_stream(), mimetype='text/event-stream')
With this refactor, we accomplish two things:通过这个重构,我们完成了两件事:
event_stream
has an additional timeout
parameter, which allows us to prevent the infinite loop in tests event_stream
有一个额外的timeout
参数,它允许我们防止测试中的无限循环
event_stream
is not a nested function, which allows us to mock it appropriately. event_stream
不是嵌套的 function,它允许我们适当地模拟它。
Ideally, we want to only specify the timeout
parameter in tests, but allow it to continue forever in production settings.理想情况下,我们只想在测试中指定timeout
参数,但允许它在生产设置中永远持续下去。 We can accomplish this through mocks:我们可以通过 mocks 来实现:
from contextlib import contextmanager
from functools import partial
from unittest import mock
@contextmanager
def mock_events():
with mock.patch(
# NOTE: For example, if your function is found in app/views/translations.py,
# then this import path would be 'app.views.translations.event_stream'
'python.import.path.to.stream_translations.event_stream',
# NOTE: Remember to import this function wherever you defined it.
partial(event_stream, timeout=1.5),
):
yield
This allows us to minimize differences between test code and production code (since the same function is run), but actually allows this function to complete in tests.这允许我们最小化测试代码和生产代码之间的差异(因为运行相同的 function),但实际上允许这个 function 在测试中完成。
With this function, your test may look something like this:使用此 function,您的测试可能如下所示:
def test_stream(client):
with mock_events():
response = client.get("/translations/stream")
assert response.data.decode()
After a while my friend solve it very easy using pytest
:过了一会儿,我的朋友使用pytest
很容易解决它:
@fixture(scope='function')
def stream_start(client: FlaskClient):
client.post("/stream/start")
sleep(4) # you get data for 4 sec
client.post("/stream/stop")
def test_output_stream(client: FlaskClient, api_start):
with client.get('/stream/output_stream') as res:
for encoded in res.iter_encoded():
if encoded:
data = json.loads(encoded.decode('UTF-8')[::])
assert real_data == data
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.