austial-testing 0.2.0__tar.gz → 0.2.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {austial_testing-0.2.0 → austial_testing-0.2.1}/.gitignore +3 -0
- {austial_testing-0.2.0 → austial_testing-0.2.1}/PKG-INFO +1 -1
- austial_testing-0.2.1/src/austial/testing/testing_module.py +139 -0
- austial_testing-0.2.0/src/austial/testing/testing_module.py +0 -64
- {austial_testing-0.2.0 → austial_testing-0.2.1}/pyproject.toml +0 -0
- {austial_testing-0.2.0 → austial_testing-0.2.1}/src/austial/testing/__init__.py +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
"""``Test.create_testing_module(...).compile()`` -- mirrors ``@nestjs/testing``.
|
|
2
|
+
|
|
3
|
+
Override anything before compiling -- providers, guards, pipes, interceptors, filters -- and
|
|
4
|
+
stub whatever you didn't register with ``use_mocker``::
|
|
5
|
+
|
|
6
|
+
module = await (
|
|
7
|
+
Test.create_testing_module(imports=[CatsModule])
|
|
8
|
+
.override_provider(CatsRepository).use_value(FakeCatsRepository())
|
|
9
|
+
.override_guard(AuthGuard).use_class(AllowAllGuard)
|
|
10
|
+
.use_mocker(lambda token: MagicMock())
|
|
11
|
+
.compile()
|
|
12
|
+
)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from collections.abc import Callable, Sequence
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
from fastapi import FastAPI
|
|
21
|
+
|
|
22
|
+
from austial.common.decorators.modules import Module
|
|
23
|
+
from austial.core.app import AustialApplication
|
|
24
|
+
from austial.core.container import Container
|
|
25
|
+
from austial.core.module_scanner import scan_module
|
|
26
|
+
from austial.core.request_context import ContextId
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class TestingModule:
|
|
30
|
+
__test__ = False # not a pytest test class, despite the name
|
|
31
|
+
|
|
32
|
+
def __init__(self, container: Container, controllers: list):
|
|
33
|
+
self._container = container
|
|
34
|
+
self._controllers = controllers
|
|
35
|
+
|
|
36
|
+
def get(self, token: Any) -> Any:
|
|
37
|
+
"""Fetches a resolved provider straight out of the DI container --
|
|
38
|
+
the whole point of testing modules: unit-test a service with its real
|
|
39
|
+
dependency graph, no manual mocking wiring required."""
|
|
40
|
+
return self._container.resolve(token)
|
|
41
|
+
|
|
42
|
+
def resolve(self, token: Any, context_id: ContextId | None = None) -> Any:
|
|
43
|
+
"""Like :meth:`get`, but also builds request-scoped providers (inside ``context_id``)."""
|
|
44
|
+
from austial.core.module_ref import ModuleRef
|
|
45
|
+
|
|
46
|
+
return self._container.resolve(ModuleRef).resolve(token, context_id)
|
|
47
|
+
|
|
48
|
+
def create_austial_application(self) -> AustialApplication:
|
|
49
|
+
"""Mirrors ``moduleRef.createNestApplication()``. Call ``await app.init()``
|
|
50
|
+
before making requests against it."""
|
|
51
|
+
fastapi_app = FastAPI(title="Austial (test)")
|
|
52
|
+
return AustialApplication(self._container, fastapi_app, self._controllers, [])
|
|
53
|
+
|
|
54
|
+
async def close(self) -> None:
|
|
55
|
+
"""Runs the termination lifecycle hooks, like ``await moduleRef.close()``."""
|
|
56
|
+
await self._container.lifecycle.run_destroy()
|
|
57
|
+
self._container.lifecycle.reset()
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class OverrideBy:
|
|
61
|
+
"""What to replace a token with: ``use_value``, ``use_class`` or ``use_factory``."""
|
|
62
|
+
|
|
63
|
+
def __init__(self, builder: TestingModuleBuilder, token: Any):
|
|
64
|
+
self._builder = builder
|
|
65
|
+
self._token = token
|
|
66
|
+
|
|
67
|
+
def use_value(self, value: Any) -> TestingModuleBuilder:
|
|
68
|
+
return self._builder._add_override({"provide": self._token, "useValue": value}) # noqa: SLF001
|
|
69
|
+
|
|
70
|
+
def use_class(self, cls: type) -> TestingModuleBuilder:
|
|
71
|
+
return self._builder._add_override({"provide": self._token, "useClass": cls}) # noqa: SLF001
|
|
72
|
+
|
|
73
|
+
def use_factory(self, factory: Callable[..., Any], inject: Sequence[Any] = ()) -> TestingModuleBuilder:
|
|
74
|
+
override = {"provide": self._token, "useFactory": factory, "inject": list(inject)}
|
|
75
|
+
return self._builder._add_override(override) # noqa: SLF001
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class TestingModuleBuilder:
|
|
79
|
+
__test__ = False
|
|
80
|
+
|
|
81
|
+
def __init__(
|
|
82
|
+
self,
|
|
83
|
+
*,
|
|
84
|
+
imports: Sequence[Any] = (),
|
|
85
|
+
controllers: Sequence[type] = (),
|
|
86
|
+
providers: Sequence[Any] = (),
|
|
87
|
+
):
|
|
88
|
+
@Module(imports=list(imports), controllers=list(controllers), providers=list(providers))
|
|
89
|
+
class _TestingRootModule:
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
self._root_module = _TestingRootModule
|
|
93
|
+
self._overrides: list[dict[str, Any]] = []
|
|
94
|
+
self._mocker: Callable[[Any], Any] | None = None
|
|
95
|
+
|
|
96
|
+
def _add_override(self, definition: dict[str, Any]) -> TestingModuleBuilder:
|
|
97
|
+
self._overrides.append(definition)
|
|
98
|
+
return self
|
|
99
|
+
|
|
100
|
+
def override_provider(self, token: Any) -> OverrideBy:
|
|
101
|
+
"""Replace the provider registered under ``token`` (a class or any other token)."""
|
|
102
|
+
return OverrideBy(self, token)
|
|
103
|
+
|
|
104
|
+
# Guards, pipes, interceptors and filters are resolved from the container by their class, so
|
|
105
|
+
# overriding one is overriding that provider -- the aliases just read better in a test.
|
|
106
|
+
override_guard = override_provider
|
|
107
|
+
override_pipe = override_provider
|
|
108
|
+
override_interceptor = override_provider
|
|
109
|
+
override_filter = override_provider
|
|
110
|
+
|
|
111
|
+
def use_mocker(self, mocker: Callable[[Any], Any]) -> TestingModuleBuilder:
|
|
112
|
+
"""``mocker(token)`` supplies a stand-in for every dependency nothing registered; return
|
|
113
|
+
``None`` to leave a token to the container's normal handling."""
|
|
114
|
+
self._mocker = mocker
|
|
115
|
+
return self
|
|
116
|
+
|
|
117
|
+
async def compile(self) -> TestingModule:
|
|
118
|
+
container = Container()
|
|
119
|
+
container.mocker = self._mocker
|
|
120
|
+
scan_result = scan_module(self._root_module, container)
|
|
121
|
+
for override in self._overrides:
|
|
122
|
+
container.register(override)
|
|
123
|
+
await container.init_async_providers()
|
|
124
|
+
return TestingModule(container, scan_result.controllers)
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
class Test:
|
|
128
|
+
"""Entry point, mirrors Nest's ``Test.createTestingModule({...})``."""
|
|
129
|
+
|
|
130
|
+
__test__ = False
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def create_testing_module(
|
|
134
|
+
*,
|
|
135
|
+
imports: Sequence[Any] = (),
|
|
136
|
+
controllers: Sequence[type] = (),
|
|
137
|
+
providers: Sequence[Any] = (),
|
|
138
|
+
) -> TestingModuleBuilder:
|
|
139
|
+
return TestingModuleBuilder(imports=imports, controllers=controllers, providers=providers)
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
"""``Test.create_testing_module(...).compile()`` -- mirrors ``@nestjs/testing``."""
|
|
2
|
-
|
|
3
|
-
from __future__ import annotations
|
|
4
|
-
|
|
5
|
-
from collections.abc import Sequence
|
|
6
|
-
from typing import Any
|
|
7
|
-
|
|
8
|
-
from fastapi import FastAPI
|
|
9
|
-
|
|
10
|
-
from austial.common.decorators.modules import Module
|
|
11
|
-
from austial.core.app import AustialApplication
|
|
12
|
-
from austial.core.container import Container
|
|
13
|
-
from austial.core.module_scanner import scan_module
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
class TestingModule:
|
|
17
|
-
def __init__(self, container: Container, controllers: list):
|
|
18
|
-
self._container = container
|
|
19
|
-
self._controllers = controllers
|
|
20
|
-
|
|
21
|
-
def get(self, token: Any) -> Any:
|
|
22
|
-
"""Fetches a resolved provider straight out of the DI container --
|
|
23
|
-
the whole point of testing modules: unit-test a service with its real
|
|
24
|
-
dependency graph, no manual mocking wiring required."""
|
|
25
|
-
return self._container.resolve(token)
|
|
26
|
-
|
|
27
|
-
def create_austial_application(self) -> AustialApplication:
|
|
28
|
-
"""Mirrors ``moduleRef.createNestApplication()``. Call ``await app.init()``
|
|
29
|
-
before making requests against it."""
|
|
30
|
-
fastapi_app = FastAPI(title="Austial (test)")
|
|
31
|
-
return AustialApplication(self._container, fastapi_app, self._controllers, [])
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
class TestingModuleBuilder:
|
|
35
|
-
def __init__(
|
|
36
|
-
self,
|
|
37
|
-
*,
|
|
38
|
-
imports: Sequence[Any] = (),
|
|
39
|
-
controllers: Sequence[type] = (),
|
|
40
|
-
providers: Sequence[Any] = (),
|
|
41
|
-
):
|
|
42
|
-
@Module(imports=list(imports), controllers=list(controllers), providers=list(providers))
|
|
43
|
-
class _TestingRootModule:
|
|
44
|
-
pass
|
|
45
|
-
|
|
46
|
-
self._root_module = _TestingRootModule
|
|
47
|
-
|
|
48
|
-
async def compile(self) -> TestingModule:
|
|
49
|
-
container = Container()
|
|
50
|
-
scan_result = scan_module(self._root_module, container)
|
|
51
|
-
return TestingModule(container, scan_result.controllers)
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
class Test:
|
|
55
|
-
"""Entry point, mirrors Nest's ``Test.createTestingModule({...})``."""
|
|
56
|
-
|
|
57
|
-
@staticmethod
|
|
58
|
-
def create_testing_module(
|
|
59
|
-
*,
|
|
60
|
-
imports: Sequence[Any] = (),
|
|
61
|
-
controllers: Sequence[type] = (),
|
|
62
|
-
providers: Sequence[Any] = (),
|
|
63
|
-
) -> TestingModuleBuilder:
|
|
64
|
-
return TestingModuleBuilder(imports=imports, controllers=controllers, providers=providers)
|
|
File without changes
|
|
File without changes
|