Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 15 additions & 8 deletions docs/testclient.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,25 @@ case you should use `client = TestClient(app, raise_server_exceptions=False)`.

### Selecting the Async backend

`TestClient.async_backend` is a dictionary which allows you to set the options
for the backend used to run tests. These options are passed to
`anyio.start_blocking_portal()`. See the [anyio documentation](https://anyio.readthedocs.io/en/stable/basics.html#backend-options)
for more information about backend options. By default, `asyncio` is used.
`TestClient` takes arguments `backend` (a string) and `backend_options` (a dictionary).
These options are passed to `anyio.start_blocking_portal()`. See the [anyio documentation](https://anyio.readthedocs.io/en/stable/basics.html#backend-options)
for more information about the accepted backend options.
By default, `asyncio` is used with default options.

To run `Trio`, set `async_backend["backend"] = "trio"`, for example:
To run `Trio`, pass `backend="trio"`. For example:

```python
def test_app()
client = TestClient(app)
client.async_backend["backend"] = "trio"
...
with TestClient(app, backend="trio") as client:
...
```

To run `asyncio` with `uvloop`, pass `backend_options={"use_uvloop": True}`. For example:

```python
def test_app()
with TestClient(app, backend_options={"use_uvloop": True}) as client:
...
```

### Testing WebSocket sessions
Expand Down
10 changes: 7 additions & 3 deletions starlette/testclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -381,13 +381,11 @@ def receive_json(self, mode: str = "text") -> typing.Any:

class TestClient(requests.Session):
__test__ = False # For pytest to not discover this up.

#: These options are passed to `anyio.start_blocking_portal()`
#: These are the default options for the constructor arguments
async_backend: typing.Dict[str, typing.Any] = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this class-level declaration still? Class vars always confuse me, but this means somebody can do:

TestClient.async_backend["backend"] = "trio"

to default all future TestClient instances to use the Trio backend, right?

I know removing this would be slightly changing the API, however in the code examples we gave we only modified instance attributes and that usage should stay the same. What's your thinking around this?

Copy link
Contributor

@graingert graingert Jun 28, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've removed it in my followup PR: #1211

"backend": "asyncio",
"backend_options": {},
}

task: "Future[None]"

def __init__(
Expand All @@ -396,8 +394,14 @@ def __init__(
base_url: str = "http://testserver",
raise_server_exceptions: bool = True,
root_path: str = "",
backend: typing.Optional[str] = None,
backend_options: typing.Optional[typing.Dict[str, typing.Any]] = None,
) -> None:
super().__init__()
self.async_backend = {
"backend": backend or self.async_backend["backend"],
"backend_options": backend_options or self.async_backend["backend_options"],
}
if _is_asgi3(app):
app = typing.cast(ASGI3App, app)
asgi_app = app
Expand Down
19 changes: 19 additions & 0 deletions tests/test_testclient.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,3 +132,22 @@ async def asgi(receive, send):
with client.websocket_connect("/") as websocket:
data = websocket.receive_json()
assert data == {"message": "test"}


def test_backend_name(request):
"""
Test that the tests are defaulting to the correct backend and that a new
instance of TestClient can be created using different backend options.
"""
# client created using monkeypatched async_backend
client1 = TestClient(mock_service)
if "trio" in request.keywords:
client2 = TestClient(mock_service, backend="asyncio")
assert client1.async_backend["backend"] == "trio"
assert client2.async_backend["backend"] == "asyncio"
elif "asyncio" in request.keywords:
client2 = TestClient(mock_service, backend="trio")
assert client1.async_backend["backend"] == "asyncio"
assert client2.async_backend["backend"] == "trio"
else:
pytest.fail("Unknown backend") # pragma: nocover