Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

respx decorator breaks pytest fixture #210

Closed
gregbrowndev opened this issue Aug 18, 2022 · 3 comments · Fixed by #213
Closed

respx decorator breaks pytest fixture #210

gregbrowndev opened this issue Aug 18, 2022 · 3 comments · Fixed by #213

Comments

@gregbrowndev
Copy link

Hi,

Uncommenting the decorator on the test below throws an error:

TypeError: test_respx() missing 1 required positional argument: 'httpx_client'

@pytest.fixture
def httpx_client() -> AsyncClient:
    # note I know we should be using `async with...`
    return httpx.AsyncClient()


async def send_request(client: AsyncClient, url: str) -> dict:
    r = await client.get(url)
    return r.json()


# @respx.mock(assert_all_called=True)  # uncomment this
def test_respx(
    respx_mock: MockRouter,
    httpx_client: AsyncClient,
):
    url = "https://example.org/"
    data = {"message": "Hello"}

    respx_mock.get(url).mock(
        return_value=httpx.Response(200, json=data)
    )

    loop = asyncio.get_event_loop()
    
    coroutine = send_request(httpx_client, url)
    actual = loop.run_until_complete(coroutine)
    
    assert data == actual

I'm doing a few things that might seem odd:

  • I want to define httpx_client as a fixture - not odd, but I haven't seen this done in any of the docs. Note, creating AsyncClient in the test works fine.
  • The test is sync but testing HTTPX AsyncClient on asyncio.get_event_loop() instead of making the pytest async. This test is trying to prove some behaviour while we migrate to async HTTPX. Our application is synchronous but we want to make some concurrent requests within a deeply buried module.

Nonetheless, this seems like a bug in respx.

Thanks

@KallieLev
Copy link

KallieLev commented Aug 18, 2022

This does seem like a bug, the MockRouter object's __call__ doesn't accept and pass any argument to the inner test function.
In the meanwhile you can work around this by initiating the mock object at the module-level, instead of decorator.

...
resp_mock = respx.mock(assert_all_called=True)

def test_respx(httpx_client: AsyncClient):
    ...

I tried to solve this but unfortunately it has taken me too long to figure it out 😅
For now I've created this test that makes sure the issue won't repeat once it's solved - #211

@lundberg
Copy link
Owner

Thanks @gregbrowndev and @KallieLev for pointing this out!

With the @respx.mock(...) decorator, the test function will receive the respx_mock instance from the decorator.

Without the decorator, the respx_mock function arg will be the respx pytest fixture.

Currently it seems to be like you're describing, i.e. a @respx.mock-decorated test together with pytest fixtures breaks.

I'd recommend, at least for now, to instead use the respx fixture and the respx marker for tests that need other pytest fixtures, rather than the respx-decorator.

@pytest.fixture
async def httpx_client():
    async with httpx.AsyncClient() as client:
        yield client


@pytest.mark.respx(assert_all_called=True)  # NOTE: optional marker for configuration
def test_respx(
    respx_mock: MockRouter,
    httpx_client: AsyncClient,
):
    ...

@lundberg
Copy link
Owner

lundberg commented Aug 25, 2022

@gregbrowndev, I managed to fix a PR that solves the bug 😉.

... though, since you're using pytest, my suggested respx_mock fixture with an optional pytest marker might still be a better option for you.

@KallieLev, thanks for #211 ... it'll be a duplicate once #213 is merged and get closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants