diff --git a/docs/api_controller/model_controller/04_parameters.md b/docs/api_controller/model_controller/04_parameters.md index 95f1665..b58ce36 100644 --- a/docs/api_controller/model_controller/04_parameters.md +++ b/docs/api_controller/model_controller/04_parameters.md @@ -219,7 +219,6 @@ class EventModelController(ModelControllerBase): list_events = ModelEndpointFactory.list( path="/", - schema_in=EventQueryParams, schema_out=EventSchema, queryset_getter=lambda self, query: self.service.get_filtered_events(query) ) diff --git a/docs/tutorial/testing.md b/docs/tutorial/testing.md index 30dc1a5..9c9632f 100644 --- a/docs/tutorial/testing.md +++ b/docs/tutorial/testing.md @@ -32,7 +32,7 @@ class TestMyMathController: } ``` -Similarly, for testing an asynchronous route function, you can use TestClientAsync as follows: +Similarly, for testing an asynchronous route function, you can use TestAsyncClient as follows: ```python from ninja_extra import api_controller, route @@ -50,8 +50,72 @@ class MyMathController: class TestMyMathController: def test_get_users_async(self): client = TestAsyncClient(MyMathController) - response = client.get('/add', query=dict(a=3, b=5)) + response = client.get('/add', query={"a": 3, "b": 5}) assert response.status_code == 200 assert response.json() == {"result": -2} ``` + +### Controllers with a static prefix + +When using `TestClient`/`TestAsyncClient` with a controller class that has a static prefix, call endpoints using the route path defined on the method (without the static prefix). The testing client wires the controller under the hood and resolves routes accordingly. + +```python +from ninja_extra import api_controller, route +from ninja_extra.testing import TestClient + + +@api_controller('/api', tags=['Users']) +class UserController: + @route.get('/users') + def list_users(self): + return [ + { + 'first_name': 'Ninja Extra', + 'username': 'django_ninja', + 'email': 'john.doe@gmail.com', + } + ] + + +def test_get_users(): + client = TestClient(UserController) + # Note: no '/api' here + response = client.get('/users') + assert response.status_code == 200 + assert response.json()[0]['username'] == 'django_ninja' +``` + +### Controllers with a path variable in the prefix + +If the controller prefix contains path parameters, include those parameters in the URL when calling the testing client. For example: + +```python +import uuid +from ninja import Schema +from ninja_extra import api_controller, route +from ninja_extra.testing import TestClient + + +class UserIn(Schema): + username: str + email: str + + +@api_controller('/users/{int:org_id}/', tags=['Users']) +class OrgUsersController: + @route.post('') + def create_user(self, org_id: int, user: UserIn): + # simulate created user + return {'id': str(uuid.uuid4()), 'org_id': org_id, 'username': user.username} + + +def test_create_user_under_param_prefix(): + client = TestClient(OrgUsersController) + response = client.post('/users/123/', json={'username': 'jane', 'email': 'jane@example.com'}) + assert response.status_code == 200 + data = response.json() + assert data['org_id'] == 123 + assert data['username'] == 'jane' + assert 'id' in data +``` diff --git a/tests/test_testclient_with_controllers.py b/tests/test_testclient_with_controllers.py new file mode 100644 index 0000000..3dcf5d5 --- /dev/null +++ b/tests/test_testclient_with_controllers.py @@ -0,0 +1,118 @@ +import uuid + +import django +import pytest +from ninja import Schema + +from ninja_extra import api_controller, route +from ninja_extra.testing import TestAsyncClient, TestClient + + +@api_controller("", tags=["Users"]) +class UserController: + @route.get("/users") + def list_users(self): + return [ + { + "first_name": "Ninja Extra", + "username": "django_ninja", + "email": "john.doe@gmail.com", + } + ] + + +class TestClientWithController: + def test_get_users(self): + client = TestClient(UserController) + response = client.get("/users") + assert response.status_code == 200 + body = response.json() + assert isinstance(body, list) and len(body) == 1 + assert body[0] == { + "first_name": "Ninja Extra", + "username": "django_ninja", + "email": "john.doe@gmail.com", + } + + +@api_controller("", tags=["Math"]) +class MyMathController: + @route.get("/add") + async def add(self, a: int, b: int): + """Return a - b to mirror the docs example.""" + return {"result": a - b} + + +class TestAsyncClientWithController: + @pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher") + @pytest.mark.asyncio + async def test_add_async(self): + client = TestAsyncClient(MyMathController) + response = await client.get("/add", query={"a": 3, "b": 5}) + assert response.status_code == 200 + assert response.json() == {"result": -2} + + +@api_controller("/api", tags=["Users"]) +class PrefixedUserController: + @route.get("/users") + def list_users(self): + return [ + { + "first_name": "Ninja Extra", + "username": "django_ninja", + "email": "john.doe@gmail.com", + } + ] + + +class TestClientWithPrefixedController: + def test_get_users_under_prefix(self): + client = TestClient(PrefixedUserController) + response = client.get("/users") + assert response.status_code == 200 + body = response.json() + assert isinstance(body, list) and len(body) == 1 + assert body[0]["username"] == "django_ninja" + + +@api_controller("/math", tags=["Math"]) +class PrefixedMathController: + @route.get("/add") + async def add(self, a: int, b: int): + return {"result": a + b} + + +class TestAsyncClientWithPrefixedController: + @pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher") + @pytest.mark.asyncio + async def test_add_async_under_prefix(self): + client = TestAsyncClient(PrefixedMathController) + response = await client.get("/add", query={"a": 3, "b": 5}) + assert response.status_code == 200 + assert response.json() == {"result": 8} + + +class UserIn(Schema): + username: str + email: str + + +@api_controller("/users/{int:org_id}/", tags=["Users"]) +class OrgUsersController: + @route.post("") + def create_user(self, org_id: int, user: UserIn): + return {"id": str(uuid.uuid4()), "org_id": org_id, "username": user.username} + + +class TestClientWithParamPrefixedController: + def test_create_user_under_param_prefix(self): + client = TestClient(OrgUsersController) + response = client.post( + "/users/123/", json={"username": "jane", "email": "jane@example.com"} + ) + assert response.status_code == 200 + body = response.json() + assert body["org_id"] == 123 + assert body["username"] == "jane" + assert "id" in body