I am trying to write tests for aiohttp application. I am using pytest-aiohttp plugin. My intention is to initialize and run the application once before first test execution and tear down after all tests finished. pytest-aiohttp fixtures like 'loop', 'test_client' are very helpful but they have scope='function' which means I can not use them from my own fixture with scope='session'. Is there a way to workaround this? And if not then what would be a proper approach for achieving my goal without using built-in fixtures?
My code is as follows (conftest.py)
#pytest.fixture()
def client(test_client, loop):
app = init(loop)
return loop.run_until_complete(test_client(app))
My tests then use this
class TestGetAlerts:
async def test_get_login_url(self, client):
resp = await client.get('/api/get_login_url')
assert resp.status == 200
So my fixture 'client' runs for every test, which is what i want to avoid
test_client fixture is a simple wrapper around TestServer and TestClient classes from aiohttp.test_utils.
You can craft your own version of the fixture with 'session' scope.
But this way has own problems: tests should be isolated, in practice it means event loop recreation for every test.
But session-level aiohttp application doesn't support such loop recreation. Thus the app should be run in separate thread which makes writing test assertions much harder.
In my practice aiohttp application starts instantly but things like DB schema migration and DB fixtures applying takes time. These activities could be implemented in session scope easy as separate fixtures but app starting/stopping should be performed inside every test.
Related
I have an asynchronous library asynpg which makes requests to an external database.
I want to have a test which mocks syncpg.connect to simulate a database connection, even when there is none:
#pytest.fixture
def mock_asyncpg(monkeypatch, mock_data):
""" Patch asyncpg.connect """
monkeypatch.setattr(asyncpg, "connect", mock_asyncio_connect(mock_data))
If I want to test using the mock I can include it:
#pytest.mark.asyncio
async def test_with_mock(mock_asyncpg):
But I'd also like to test with the real, unmocked, version of asyncpg, but asyncpg has been monkey-patched everywhere, and all tests see the monkey-patched version.
I assume this is happening because my tests are running asynchronously so they all see the same version of asyncpg
I see that there is a library for asynchronous monkey patching, but I wonder if there's a better pattern I should be using. Maybe something to do with CoroutineMock?
I’m developing database based flask application (using flask-sqlalchemy). I use fixtures to define individual pieces of test data.
#pytest.fixture(scope='class')
def model_a(db):
a = ModelA()
db.session.add(a)
db.session.commit()
return a
#pytest.fixture(scope='class')
def model_b(db, a):
b = ModelB(a=a)
db.session.add(b)
db.session.commit()
return b
# …
While it works to call db.session.commit() for each and every test object it would be more efficient to call it only once right before executing the actual tests.
Is there a way to run db.session.commit() before every test, after all fixtures are loaded, but only if the test directly or indirectly needs db?
Things that I don’t think that they will work:
A python_runtest_setup-hook doesn’t seem to be able to access fixtures or to determine whether the db fixture is loaded for/required by the test.
A autouse fixture would need to depend on db and thus make all tests use the db fixture. Also I couldn’t find a way to make it executed last.
You can't specify fixtures ordering other than indirectly (fixtures depending on other fixtures), see discussion in issue #1216. You can access both fixture names and fixture values in hooks though, so using a hook is actually a good idea. However, pytest_runtest_setup is too early for all fixtures to be executed; use pytest_pyfunc_call instead. Example:
from _pytest.fixtures import FixtureRequest
def pytest_pyfunc_call(pyfuncitem):
if 'db' in pyfuncitem.fixturenames:
db = FixtureRequest(pyfuncitem, _ispytest=True).getfixturevalue('db')
db.session.commit()
# ensure this hook returns None, or your underlying test function won't be executed.
Warning: pytest.FixtureRequest() is considered non-public by the pytest maintainers for anything except type-hinting. That’s why its use issues a deprecation warning without the _ispytest=True flag. The API might change without a major release. There is no public API yet that supports this use-case. (Last updated for pytest 6.2.5)
I am setting up the test platform for a multi-tenant system.
For each written test, I want to create a test for each tenant and wrap the entire test run to change the database connexion and some thread local variable before the test without breaking the database teardown where it flushes.
During my trial-and-error process, I've resorted to climbing higher and higher in the pytest hook chain: I started with pytest_generate_tests to create a test for each tenant with an associated fixture, but the teardown failed, I've ended up at the following idea:
def pytest_runtestloop(session):
for tenant in range(settings.TENANTS.keys()):
with set_current_tenant(tenant):
with environ({'DJANGO_CONFIGURATION': f'Test{tenant.capitalize()}Config'}):
session.config.pluginmanager.getplugin("main").pytest_runtestloop(session)
return True
Although this does not work (since django-configurations loads the settings during the earlier pytest_load_initial_conftests phase), this example should give an idea of what I am trying to achieve.
The big roadblock: The default database connection needs to point to the current tenant's database before any fixtures are loaded and after flush is ran.
I have disabled pytest-django's default session fixture mechanism and plan on using a external database for tests :
#pytest.fixture(scope='session')
def django_db_setup():
pass
I could have a wrapper python script that calls pytest multiple times with the right config, but I would lose a lot of nice tooling.
I recently started coding using pytest fixtures and have setup function which bring up and configures various nodes in the network.
For writing automation test cases, creating setup is one time activity and is error prone as well. As this is one time hence, scope is set to "module" or "session"
While implementing frixtures, as per pytest documenation / recommendation, I implemented it somewhat like this:
class xxx:
#pytest.fixture(scop = "module", autouse=True)
def setup(self, request):
-----lines of code ---------
def teardown():
------cod for tear down --------
request.addfinalizer(teardown)
How to handle the situation when an error occurs in setup fixture, ideally it should invoke teardown and execution is stopped at that point i.e. test cases should not get executed further.
Thanks for you reply.
I'm having some trouble trying to figure how to test my app architecture. I've already 60% of my website complete, with full unit testing coverage (over all utility/lib functions, celery tasks as simple functions, and so on).
The problem arises when I try to test django views (plain functions) that executes celery tasks (delay method).
Example:
def myview(request):
...
mytask.delay(myval)
...
What should be the right way to test that scenes without really generating a new task execution?
The obvious way is to set up a condition before every task delay call, executing it only if it's not in test environment, but it seems really dirty.
Any tip?
Use CELERY_ALWAYS_EAGER settings for test run.
It make the function to be called immediately instead of running it as a task.
Example django settings snippet:
if 'test' in sys.argv:
CELERY_ALWAYS_EAGER = True