This repository has been archived by the owner on Sep 12, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feature(testing): pytest plugin (#246)
Adds an initial pass at a pytest plugin for testing `starlite-saqlalchemy` applications. Includes the following fixtures: * `is_unit_test`: a boolean that distinguishes between unit and integration tests * `_patch_http_close`: an autouse fixture that prevents any globally instantiated http clients being closed between tests * `_patch_sqlalchemy_plugin`: autouse fixture that patches the sqlalchemy plugin `on_shutdown` method for unittests * `_patch_worker`: autouse fixture that patches the worker `on_app_startup` and `stop` methods for unit tests * `app`: fixture that uses a defined import path to inject an application instance. Default path is `app.main:create_app` * `client`: fixture that injects a starlite `TestClient` instance bound to the same app given by the `app` fixture * `cap_logger`: a structlog capturing logger for inspecting log output Closes #106 Closes #232
- Loading branch information
1 parent
740f3f7
commit 7480356
Showing
28 changed files
with
1,367 additions
and
999 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
# Pytest Plugin | ||
|
||
The nature of applications built with the `starlite-saqlalchemy` pattern is that they rely heavily | ||
on connected services. | ||
|
||
Abstraction of [PostgreSQL][2] and [Redis][3] connectivity boilerplate is a nice convenience, | ||
however to successfully patch the application for testing requires deeper knowledge of the | ||
implementation than would be otherwise necessary. | ||
|
||
So, `starlite-saqlalchemy` ships with a selection of [pytest fixtures][1] that are often necessary | ||
when building applications such as these. | ||
|
||
## `app` | ||
|
||
The `app` fixture provides an instance of a `Starlite` application. | ||
|
||
```python | ||
from __future__ import annotations | ||
|
||
from starlite import Starlite | ||
|
||
|
||
def test_app_fixture(app: Starlite) -> None: | ||
assert isinstance(app, Starlite) | ||
``` | ||
|
||
The value of Pytest ini option, `test_app` is used to determine the application to load. | ||
|
||
```toml | ||
# pyproject.toml | ||
|
||
[tool.pytest.ini_options] | ||
test_app = "app.main:create_app" | ||
``` | ||
|
||
If no value is configured for the `test_app` ini option, the default location of | ||
`"app.main:create_app"` is searched. | ||
|
||
The value of the `test_app` ini option can either point to an application factory or `Starlite` | ||
instance. | ||
|
||
If the object found at the import path is not a `Starlite` instance, the fixture assumes it is | ||
an application factory, and will call the object and return the response. | ||
|
||
The value of `test_app` is resolved using the uvicorn `import_from_string()` function, so it | ||
supports the same format as `uvicorn` supports for its `app` and `factory` parameters. | ||
|
||
## `client` | ||
|
||
A `starlite.testing.TestClient` instance, wired to the same application that is produced by the | ||
`app` fixture. | ||
|
||
## `cap_logger` | ||
|
||
The `cap_logger` fixture provides an instance of [`structlog.testing.CapturingLogger`][4]. | ||
|
||
```python | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
from structlog.testing import CapturedCall | ||
|
||
if TYPE_CHECKING: | ||
from structlog.testing import CapturingLogger | ||
|
||
|
||
def test_app_fixture(cap_logger: CapturingLogger) -> None: | ||
cap_logger.info("hello") | ||
cap_logger.info("hello", when="again") | ||
assert cap_logger.calls == [ | ||
CapturedCall(method_name="info", args=("hello",), kwargs={}), | ||
CapturedCall(method_name="info", args=("hello",), kwargs={"when": "again"}), | ||
] | ||
``` | ||
|
||
The `cap_logger` fixture will capture any `structlog` calls made by the starlite application or the | ||
SAQ worker, so that they can be inspected as part of tests. | ||
|
||
```python | ||
from __future__ import annotations | ||
|
||
from typing import TYPE_CHECKING | ||
|
||
from httpx import AsyncClient | ||
|
||
if TYPE_CHECKING: | ||
from starlite import Starlite | ||
from structlog.testing import CapturingLogger | ||
|
||
|
||
async def test_health_logging_skipped( | ||
app: Starlite, cap_logger: CapturingLogger | ||
) -> None: | ||
"""Test that calls to the health check route are not logged.""" | ||
|
||
async with AsyncClient(app=app, base_url="http://testserver") as client: | ||
response = await client.get("/health") | ||
assert response.status_code == 200 | ||
|
||
assert [] == cap_logger.calls | ||
``` | ||
|
||
## is_unit_test | ||
|
||
The `is_unit_test` fixture returns a `bool` that indicates if the test suite believes it is running | ||
a unit test, or an integration test. | ||
|
||
To determine this, we compare the path of the running test to the value of the Pytest ini option | ||
`unit_test_pattern`, which by default is `"^.*/tests/unit/.*$"`. | ||
|
||
This fixture is used to make fixtures behave differently between unit and integration test contexts. | ||
|
||
## _patch_http_close | ||
|
||
This is an [`autouse` fixture][5], that prevents HTTP clients that are defined in the global scope | ||
from being closed. | ||
|
||
The application is configured to close all instantiated HTTP clients on app shutdown, however when | ||
apps are defined in a global/class scope, a test that runs after the first application shutdown in | ||
the test suite would fail. | ||
|
||
## _patch_sqlalchemy_plugin | ||
|
||
This is an [`autouse` fixture][5], that mocks out the `on_shutdown` method of the SQLAlchemy config | ||
object for unit tests. | ||
|
||
## _patch_worker | ||
|
||
This is an [`autouse` fixture][5], that mocks out the `on_app_startup` and `stop` methods of | ||
`worker.Worker` type for unit tests. | ||
|
||
[1]: https://docs.pytest.org/en/latest/explanation/fixtures.html#about-fixtures | ||
[2]: https://www.postgresql.org/ | ||
[3]: https://redis.io | ||
[4]: https://www.structlog.org/en/stable/api.html#structlog.testing.CapturingLogger | ||
[5]: https://docs.pytest.org/en/6.2.x/fixture.html#autouse-fixtures-fixtures-you-don-t-have-to-request |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.