Skip to content

Commit e826eaf

Browse files
authored
Merge pull request #301 from eadwinCode/testing_docs_update
docs(testing): Expand testing guide with TestClient/TestAsyncClient and prefix examples
2 parents 4a2be33 + d9d126e commit e826eaf

File tree

3 files changed

+184
-3
lines changed

3 files changed

+184
-3
lines changed

docs/api_controller/model_controller/04_parameters.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -219,7 +219,6 @@ class EventModelController(ModelControllerBase):
219219

220220
list_events = ModelEndpointFactory.list(
221221
path="/",
222-
schema_in=EventQueryParams,
223222
schema_out=EventSchema,
224223
queryset_getter=lambda self, query: self.service.get_filtered_events(query)
225224
)

docs/tutorial/testing.md

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class TestMyMathController:
3232
}
3333

3434
```
35-
Similarly, for testing an asynchronous route function, you can use TestClientAsync as follows:
35+
Similarly, for testing an asynchronous route function, you can use TestAsyncClient as follows:
3636

3737
```python
3838
from ninja_extra import api_controller, route
@@ -50,8 +50,72 @@ class MyMathController:
5050
class TestMyMathController:
5151
def test_get_users_async(self):
5252
client = TestAsyncClient(MyMathController)
53-
response = client.get('/add', query=dict(a=3, b=5))
53+
response = client.get('/add', query={"a": 3, "b": 5})
5454
assert response.status_code == 200
5555
assert response.json() == {"result": -2}
5656

5757
```
58+
59+
### Controllers with a static prefix
60+
61+
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.
62+
63+
```python
64+
from ninja_extra import api_controller, route
65+
from ninja_extra.testing import TestClient
66+
67+
68+
@api_controller('/api', tags=['Users'])
69+
class UserController:
70+
@route.get('/users')
71+
def list_users(self):
72+
return [
73+
{
74+
'first_name': 'Ninja Extra',
75+
'username': 'django_ninja',
76+
'email': '[email protected]',
77+
}
78+
]
79+
80+
81+
def test_get_users():
82+
client = TestClient(UserController)
83+
# Note: no '/api' here
84+
response = client.get('/users')
85+
assert response.status_code == 200
86+
assert response.json()[0]['username'] == 'django_ninja'
87+
```
88+
89+
### Controllers with a path variable in the prefix
90+
91+
If the controller prefix contains path parameters, include those parameters in the URL when calling the testing client. For example:
92+
93+
```python
94+
import uuid
95+
from ninja import Schema
96+
from ninja_extra import api_controller, route
97+
from ninja_extra.testing import TestClient
98+
99+
100+
class UserIn(Schema):
101+
username: str
102+
email: str
103+
104+
105+
@api_controller('/users/{int:org_id}/', tags=['Users'])
106+
class OrgUsersController:
107+
@route.post('')
108+
def create_user(self, org_id: int, user: UserIn):
109+
# simulate created user
110+
return {'id': str(uuid.uuid4()), 'org_id': org_id, 'username': user.username}
111+
112+
113+
def test_create_user_under_param_prefix():
114+
client = TestClient(OrgUsersController)
115+
response = client.post('/users/123/', json={'username': 'jane', 'email': '[email protected]'})
116+
assert response.status_code == 200
117+
data = response.json()
118+
assert data['org_id'] == 123
119+
assert data['username'] == 'jane'
120+
assert 'id' in data
121+
```
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import uuid
2+
3+
import django
4+
import pytest
5+
from ninja import Schema
6+
7+
from ninja_extra import api_controller, route
8+
from ninja_extra.testing import TestAsyncClient, TestClient
9+
10+
11+
@api_controller("", tags=["Users"])
12+
class UserController:
13+
@route.get("/users")
14+
def list_users(self):
15+
return [
16+
{
17+
"first_name": "Ninja Extra",
18+
"username": "django_ninja",
19+
"email": "[email protected]",
20+
}
21+
]
22+
23+
24+
class TestClientWithController:
25+
def test_get_users(self):
26+
client = TestClient(UserController)
27+
response = client.get("/users")
28+
assert response.status_code == 200
29+
body = response.json()
30+
assert isinstance(body, list) and len(body) == 1
31+
assert body[0] == {
32+
"first_name": "Ninja Extra",
33+
"username": "django_ninja",
34+
"email": "[email protected]",
35+
}
36+
37+
38+
@api_controller("", tags=["Math"])
39+
class MyMathController:
40+
@route.get("/add")
41+
async def add(self, a: int, b: int):
42+
"""Return a - b to mirror the docs example."""
43+
return {"result": a - b}
44+
45+
46+
class TestAsyncClientWithController:
47+
@pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher")
48+
@pytest.mark.asyncio
49+
async def test_add_async(self):
50+
client = TestAsyncClient(MyMathController)
51+
response = await client.get("/add", query={"a": 3, "b": 5})
52+
assert response.status_code == 200
53+
assert response.json() == {"result": -2}
54+
55+
56+
@api_controller("/api", tags=["Users"])
57+
class PrefixedUserController:
58+
@route.get("/users")
59+
def list_users(self):
60+
return [
61+
{
62+
"first_name": "Ninja Extra",
63+
"username": "django_ninja",
64+
"email": "[email protected]",
65+
}
66+
]
67+
68+
69+
class TestClientWithPrefixedController:
70+
def test_get_users_under_prefix(self):
71+
client = TestClient(PrefixedUserController)
72+
response = client.get("/users")
73+
assert response.status_code == 200
74+
body = response.json()
75+
assert isinstance(body, list) and len(body) == 1
76+
assert body[0]["username"] == "django_ninja"
77+
78+
79+
@api_controller("/math", tags=["Math"])
80+
class PrefixedMathController:
81+
@route.get("/add")
82+
async def add(self, a: int, b: int):
83+
return {"result": a + b}
84+
85+
86+
class TestAsyncClientWithPrefixedController:
87+
@pytest.mark.skipif(django.VERSION < (3, 1), reason="requires django 3.1 or higher")
88+
@pytest.mark.asyncio
89+
async def test_add_async_under_prefix(self):
90+
client = TestAsyncClient(PrefixedMathController)
91+
response = await client.get("/add", query={"a": 3, "b": 5})
92+
assert response.status_code == 200
93+
assert response.json() == {"result": 8}
94+
95+
96+
class UserIn(Schema):
97+
username: str
98+
email: str
99+
100+
101+
@api_controller("/users/{int:org_id}/", tags=["Users"])
102+
class OrgUsersController:
103+
@route.post("")
104+
def create_user(self, org_id: int, user: UserIn):
105+
return {"id": str(uuid.uuid4()), "org_id": org_id, "username": user.username}
106+
107+
108+
class TestClientWithParamPrefixedController:
109+
def test_create_user_under_param_prefix(self):
110+
client = TestClient(OrgUsersController)
111+
response = client.post(
112+
"/users/123/", json={"username": "jane", "email": "[email protected]"}
113+
)
114+
assert response.status_code == 200
115+
body = response.json()
116+
assert body["org_id"] == 123
117+
assert body["username"] == "jane"
118+
assert "id" in body

0 commit comments

Comments
 (0)