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.
@@ -10,3 +10,6 @@ dist/
10
10
  build/
11
11
  *.egg-info/
12
12
  .DS_Store
13
+
14
+ .claude/settings.local.json
15
+ coverage.xml
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: austial-testing
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: TestingModule builder for overriding providers in unit/e2e tests -- mirrors @nestjs/testing.
5
5
  Requires-Python: >=3.11
6
6
  Requires-Dist: austial-common
@@ -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)