简体   繁体   中英

Unit test a listener in Sanic without starting the app server

Assuming I have this listener defined in my Sanic app:

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

If I want to unit test this function (with pytest) and I import it in a unit test file with from my.app import db_setup , it seems the test actually starts serving the app, as pytest outputs:

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

Now, I know that I can remove the effects of the decorator by doing db_setup = db_setup.__wrapped__ , but in order to do this I actually need to import db_setup , which is where the Sanic server fires up.

Is there a way of removing the effects of the decorator at import time?

LE: I've tried patching the Sanic app as follows:

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()

but now I get a RuntimeError: Cannot run the event loop while another loop is running for the mocker.patch('myapp.app.app') line.

I am going to make a few assumptions here, so I may need to amend this answer if there are some clarifications.

Before starting, it should be noted that the decorator itself will not start your web server. That will run in one of two scenarios:

  1. You are running app.run() somewhere in the global scope
  2. You are using the Sanic TestClient , which specifically operates by running your application's web server

Now, from what I can understand, you are trying to run db_setup in a test manually by calling it as a function, but you do not want it to attach as a listener to the application in your tests.

You can get access to all of your application instance's listeners in the app.listeners property. Therefore one solution would be something like this:

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

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

Like I said earlier, this will just empty out your listeners. It does not actually impact your application starting, so I am not sure it has the utility that you are looking for.


You should be able to have something like this:

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

For the sake of ease, it is all in the same scope, but even if the test functions, the application instance, and the listener are spread across different modules, the end result is the same: You can run db_setup as any other function and it does not matter if it is registered as a listener or not.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM