-
Notifications
You must be signed in to change notification settings - Fork 38
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #105 from reagento/develop
v0.7.0
- Loading branch information
Showing
71 changed files
with
1,734 additions
and
976 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 was deleted.
Oops, something went wrong.
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,19 @@ | ||
from sqlite3 import Connection | ||
from typing import Annotated | ||
|
||
from fastapi import FastAPI, APIRouter | ||
|
||
from dishka.integrations.fastapi import FromDishka, inject | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.get("/") | ||
@inject | ||
async def index(connection: Annotated[Connection, FromDishka()]) -> str: | ||
connection.execute("select 1") | ||
return "Ok" | ||
|
||
|
||
app = FastAPI() | ||
app.include_router(router) |
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,17 @@ | ||
from fastapi import FastAPI | ||
|
||
from dishka import make_async_container | ||
from dishka.integrations.fastapi import setup_dishka | ||
|
||
|
||
def create_app() -> FastAPI: | ||
app = FastAPI() | ||
app.include_router(router) | ||
return app | ||
|
||
|
||
def create_production_app(): | ||
app = create_app() | ||
container = make_async_container(ConnectionProvider("sqlite:///")) | ||
setup_dishka(container, app) | ||
return app |
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,21 @@ | ||
from collections.abc import Iterable | ||
from sqlite3 import connect, Connection | ||
|
||
from dishka import Provider, Scope, provide, make_async_container | ||
from dishka.integrations.fastapi import setup_dishka | ||
|
||
|
||
class ConnectionProvider(Provider): | ||
def __init__(self, uri): | ||
super().__init__() | ||
self.uri = uri | ||
|
||
@provide(scope=Scope.REQUEST) | ||
def get_connection(self) -> Iterable[Connection]: | ||
conn = connect(self.uri) | ||
yield conn | ||
conn.close() | ||
|
||
|
||
container = make_async_container(ConnectionProvider("sqlite:///")) | ||
setup_dishka(container, app) |
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,37 @@ | ||
from sqlite3 import Connection | ||
from unittest.mock import Mock | ||
|
||
import pytest | ||
import pytest_asyncio | ||
from fastapi.testclient import TestClient | ||
|
||
from dishka import Provider, Scope, provide, make_async_container | ||
from dishka.integrations.fastapi import setup_dishka | ||
|
||
|
||
class MockConnectionProvider(Provider): | ||
@provide(scope=Scope.APP) | ||
def get_connection(self) -> Connection: | ||
connection = Mock() | ||
connection.execute = Mock(return_value="1") | ||
return connection | ||
|
||
|
||
@pytest.fixture | ||
def container(): | ||
container = make_async_container(MockConnectionProvider()) | ||
yield container | ||
container.close() | ||
|
||
|
||
@pytest.fixture | ||
def client(container): | ||
app = create_app() | ||
setup_dishka(container, app) | ||
with TestClient(app) as client: | ||
yield client | ||
|
||
|
||
@pytest_asyncio.fixture | ||
async def connection(container): | ||
return await container.get(Connection) |
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,44 @@ | ||
Testing with dishka | ||
*************************** | ||
|
||
Testing you code does not always require the whole application to be started. You can have unit tests for separate components and even integration tests which check only specific links. In many cases you do not need IoC-container: you create objects with a power of **Dependency Injection** and not framework. | ||
|
||
For other cases which require calling functions located on application boundaries you need a container. These cases include testing you view functions with mocks of business logic and testing the application as a whole. Comparing to a production mode you will still have same implementations for some classes and others will be replaced with mocks. Luckily, in ``dishka`` your container is not an implicit global thing and can be replaced easily. | ||
|
||
There are many options to make providers with mock objects. If you are using ``pytest`` then you can | ||
|
||
* use fixtures to configure mocks and then pass those objects to a provider | ||
* create mocks in a provider and retrieve them in pytest fixtures from a container | ||
|
||
The main limitation here is that a container itself cannot be adjusted after creation. You can configure providers whenever you want before you make a container. Once it is created dependency graph is build and validated, and all you can do is to provide context data when entering a scope. | ||
|
||
|
||
Example | ||
=================== | ||
|
||
Imagine, you have a service built with FastAPI: | ||
|
||
.. literalinclude:: ./app_before.py | ||
|
||
And a container: | ||
|
||
.. literalinclude:: ./container_before.py | ||
|
||
First of all - split your application factory and and container setup. | ||
|
||
.. literalinclude:: ./app_factory.py | ||
|
||
Create a provider with you mock objects. You can still use production providers and override dependencies in a new one. Or you can build container only with new providers. It depends on the structure of your application and type of a test. | ||
|
||
.. literalinclude:: ./fixtures.py | ||
|
||
Write tests. | ||
|
||
.. literalinclude:: ./sometest.py | ||
|
||
|
||
Bringing all together | ||
============================ | ||
|
||
|
||
.. literalinclude:: ./test_example.py |
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,7 @@ | ||
from unittest.mock import Mock | ||
|
||
|
||
async def test_controller(client: TestClient, connection: Mock): | ||
response = client.get("/") | ||
assert response.status_code == 200 | ||
connection.execute.assertCalled() |
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,81 @@ | ||
from collections.abc import Iterable | ||
from sqlite3 import Connection, connect | ||
from typing import Annotated | ||
from unittest.mock import Mock | ||
|
||
import pytest | ||
import pytest_asyncio | ||
from fastapi import APIRouter, FastAPI | ||
from fastapi.testclient import TestClient | ||
|
||
from dishka import Provider, Scope, make_async_container, provide | ||
from dishka.integrations.fastapi import FromDishka, inject, setup_dishka | ||
|
||
router = APIRouter() | ||
|
||
|
||
@router.get("/") | ||
@inject | ||
async def index(connection: Annotated[Connection, FromDishka()]) -> str: | ||
connection.execute("select 1") | ||
return "Ok" | ||
|
||
|
||
def create_app() -> FastAPI: | ||
app = FastAPI() | ||
app.include_router(router) | ||
return app | ||
|
||
|
||
class ConnectionProvider(Provider): | ||
def __init__(self, uri): | ||
super().__init__() | ||
self.uri = uri | ||
|
||
@provide(scope=Scope.REQUEST) | ||
def get_connection(self) -> Iterable[Connection]: | ||
conn = connect(self.uri) | ||
yield conn | ||
conn.close() | ||
|
||
|
||
def create_production_app(): | ||
app = create_app() | ||
container = make_async_container(ConnectionProvider("sqlite:///")) | ||
setup_dishka(container, app) | ||
return app | ||
|
||
|
||
class MockConnectionProvider(Provider): | ||
@provide(scope=Scope.APP) | ||
def get_connection(self) -> Connection: | ||
connection = Mock() | ||
connection.execute = Mock(return_value="1") | ||
return connection | ||
|
||
|
||
@pytest_asyncio.fixture | ||
async def container(): | ||
container = make_async_container(MockConnectionProvider()) | ||
yield container | ||
await container.close() | ||
|
||
|
||
@pytest.fixture | ||
def client(container): | ||
app = create_app() | ||
setup_dishka(container, app) | ||
with TestClient(app) as client: | ||
yield client | ||
|
||
|
||
@pytest_asyncio.fixture | ||
async def connection(container): | ||
return await container.get(Connection) | ||
|
||
|
||
@pytest.mark.asyncio | ||
async def test_controller(client: TestClient, connection: Mock): | ||
response = client.get("/") | ||
assert response.status_code == 200 | ||
connection.execute.assert_called() |
Oops, something went wrong.