django-ninja icon indicating copy to clipboard operation
django-ninja copied to clipboard

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

Open arpanpreneur opened this issue 1 year ago • 3 comments

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):

  • Python version: 3.11
  • Django version: '4.2.13'
  • Django-Ninja version: '1.1.0'
  • Pydantic version: '2.7.4'

arpanpreneur avatar Jun 17 '24 19:06 arpanpreneur

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:

  • Python: 3.12.2
  • django-ninja: 1.2.1
  • django: 5.0.4
  • pytest: 8.2.2
  • pytest-django: 4.8.0

oliverjwroberts avatar Jul 20 '24 12:07 oliverjwroberts

@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.

arpanpreneur avatar Aug 23 '24 12:08 arpanpreneur

@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

vitalik avatar Aug 23 '24 12:08 vitalik

@vitalik Thank you so much, your solution actually worked for us.

arpanpreneur avatar Sep 24 '24 11:09 arpanpreneur