vitalik / django-ninja

💨 Fast, Async-ready, Openapi, type hints based framework for building APIs
https://django-ninja.dev
MIT License
6.93k stars 420 forks source link

[BUG] Cannot use Django's Test Client when Ninja TestClient is used in a pytest suite #1195

Open arpanpreneur opened 2 months ago

arpanpreneur commented 2 months ago

Describe the bug I have the following pytest tests, running in the same order as below. The first one uses the NinjaTestClient. Once this client1.get("/whoami", user=simple_user) is executed, the next test using Django Test Client fails with the exception Router@'/access-control/' has already been attached to API NinjaAPI:1.0.0. The stack trace is also pasted below.

from ninja.testing import TestClient as NinjaTestClient
...

@pytest.mark.django_db
def test_api_whoami_happy_path(simple_user):
    assert True
    client1 = NinjaTestClient(router)

    response_happy: NinjaResponse = client1.get("/whoami", user=simple_user)
    assert response_happy.status_code == 200

    user: AppUserResponseSchema = AppUserResponseSchema.model_validate(
        response_happy.json()
    )
    assert user.id == simple_user.id

    response_sad: NinjaResponse = client1.get("/whoami")
    assert response_sad.status_code == 401

@pytest.mark.django_db
def test_authenticated_tenanted_request(
    client: DjangoTestClient,
    simple_user: AppUser,
    simple_tenant: AppTenant,
    fake_token_generator: FakeAuth0TokenGenerator,
):
    simple_tenant.app_users.add(simple_user)
    access_token = fake_token_generator.get_fake_token_for_user(simple_user)

    response: HttpResponse = client.get(
        "/api/v1/access-control/tenant/access-check",
        HTTP_AUTHORIZATION=f"Bearer {access_token}",
        HTTP_TENANT_ID=f"{simple_tenant.id}",
    )

    assert response.status_code == 200

    response_data = response.json()
    parsed_data = TenantedAccessCheckResponseSchema.model_validate(response_data)

    assert parsed_data.app_tenant.id == simple_tenant.id
    assert parsed_data.app_user.id == simple_user.id
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <ninja.router.Router object at 0x109b1a490>, prefix = '/access-control/'

    def build_routers(self, prefix: str) -> List[Tuple[str, "Router"]]:
        if self.api is not None:
            from ninja.main import debug_server_url_reimport

            if not debug_server_url_reimport():
>               raise ConfigError(
                    f"Router@'{prefix}' has already been attached to API"
                    f" {self.api.title}:{self.api.version} "
                )
E               ninja.errors.ConfigError: Router@'/access-control/' has already been attached to API NinjaAPI:1.0.0

../../../Library/Caches/pypoetry/virtualenvs/some-backend-adgzL-V0-py3.11/lib/python3.11/site-packages/ninja/router.py:367: ConfigError

Versions (please complete the following information):

oliverjwroberts commented 1 month ago

I too am getting a similar error, however, I have an entire test_x.py script that uses Django's default test Client because it's easier to work with the request's session object, whereas all other test scripts use Django Ninja's TestClient.

Interestingly, when I run the entire test suite, I get the error ninja.errors.ConfigError: Router@'/basket/' has already been attached to API NinjaAPI:1.0.0, but when I invoke pytest path/to/test_x.py in isolation, it passes.

Versions:

arpanpreneur commented 3 weeks ago

@vitalik Would it be possible for you to take a look into it? Our team is really enjoying Ninja-API, if this could be fixed, our test suite would be even simpler. Currently we are having to write integration test for each API.

If you could even confirm the root cause of this issue, I can help fixing it by submitting a PR.

vitalik commented 3 weeks ago

@arpanpreneur

sometimes this error means some exceptions during import (or circular imports)

you can try skip check with setting NINJA_SKIP_REGISTRY=on enviroment

see example here how it's done in django ninja test suites

https://github.com/vitalik/django-ninja/blob/b655532f0cc438be630b3c241490929ac88aab57/tests/conftest.py#L17-L18

or try with

NINJA_SKIP_REGISTRY=on pytest