在不启动应用程序服务器的情况下对 Sanic 中的侦听器进行单元测试

Unit test a listener in Sanic without starting the app server

假设我在我的 Sanic 应用程序中定义了这个侦听器:

@app.before_server_start
async def db_setup(*args):
    # ... set up the DB as I wish for the app

如果我想对这个函数进行单元测试(使用 pytest)并使用 from my.app import db_setup 将其导入到单元测试文件中,似乎测试实际上开始为应用程序提供服务,因为 pytest 输出:

[INFO] Goin' Fast @ http://0.0.0.0:8000
[INFO] Starting worker [485]

现在,我知道我可以通过 db_setup = db_setup.__wrapped__ 删除装饰器的效果,但为了做到这一点,我实际上需要导入 db_setup,这是 Sanic 服务器触发的地方向上。

有没有办法在导入时删除装饰器的效果?

LE:我试过如下修补 Sanic 应用程序:

async def test_stuff(mocker):
    mocker.patch('myapp.app.app')  # last '.app' being `app = Sanic('MyApp')`
    imp = importlib.import_module('myapp.app')
    db_setup = getattr(imp, 'db_setup')
    await db_setup()

但现在我得到了 mocker.patch('myapp.app.app') 行的 RuntimeError: Cannot run the event loop while another loop is running

我将在这里做一些假设,所以如果有一些澄清,我可能需要修改这个答案。

在开始之前,应该注意装饰器本身不会启动您的网络服务器。这将 运行 在以下两种情况之一:

  1. 您正在运行宁app.run()全局范围内的某个地方
  2. 您正在使用 Sanic TestClient,它专门通过 运行 连接您的应用程序的 Web 服务器
  3. 来运行

现在,据我所知,您正试图通过将其作为函数调用来手动 运行 db_setup 测试,但您 没有 希望它作为侦听器附加到您的测试中的应用程序。

您可以在 app.listeners 属性 中访问所有应用程序实例的侦听器。因此,一种解决方案是这样的:

# conftest.py
import pytest
from some.place import app as myapp

@pytest.fixture
def app():
    myapp.listeners = {}
    return myapp

正如我之前所说,这只会清空您的听众。它实际上不会影响您的应用程序启动,所以我不确定它是否具有您正在寻找的实用程序。


你应该可以拥有这样的东西:

from unittest.mock import Mock

import pytest
from sanic import Request, Sanic, json

app = Sanic(__name__)


@app.get("/")
async def handler(request: Request):
    return json({"foo": "bar"})


@app.before_server_start
async def db_setup(app, _):
    app.ctx.db = 999


@pytest.mark.asyncio
async def test_sample():
    await db_setup(app, Mock())

    assert app.ctx.db == 999

为了方便,都在同一个范围内,但即使测试函数、应用实例、监听器分布在不同的模块中,最终的结果也是一样的:你可以运行 db_setup 与任何其他函数一样,是否注册为侦听器并不重要。