lundberg / respx

Mock HTTPX with awesome request patterns and response side effects 🦋

Home Page:https://lundberg.github.io/respx

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Confusion around assert_all_mocked behavior

sihrc opened this issue · comments

@respx.mock(assert_all_mocked=True)
async def test_httpx_all_mocked_true():

    async with httpx.AsyncClient() as client:
        request = respx.get("http://test.com/id").mock(
            return_value=httpx.Response(200, content="test")
        )
        resp = await client.get("http://test.com/id")
        assert resp.content == b"test"


@respx.mock(assert_all_mocked=False)
async def test_httpx_all_mocked_false():

    async with httpx.AsyncClient() as client:
        request = respx.get("http://test.com/id").mock(
            return_value=httpx.Response(200, content="test")
        )
        resp = await client.get("http://test.com/id")
        assert resp.content == b"test"


@respx.mock
async def test_httpx_all_mocked_none():

    async with httpx.AsyncClient() as client:
        request = respx.get("http://test.com/id").mock(
            return_value=httpx.Response(200, content="test")
        )
        resp = await client.get("http://test.com/id")
        assert resp.content == b"test"

Given these 3 identical tests with different assert_all_mocked inputs, here is the output:

================================== FAILURES ==================================
_________________________ test_httpx_all_mocked_true _________________________

    @respx.mock(assert_all_mocked=True)
    async def test_httpx_all_mocked_true():

        async with httpx.AsyncClient() as client:
            request = respx.get("http://test.com/id").mock(
                return_value=httpx.Response(200, content="test")
            )
>           resp = await client.get("http://test.com/id")

tests/routes/test_twitch_users_routes.py:19:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1729: in get
    return await self.request(
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1506: in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1593: in send
    response = await self._send_handling_auth(
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1621: in _send_handling_auth
    response = await self._send_handling_redirects(
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1658: in _send_handling_redirects
    response = await self._send_single_request(request)
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1695: in _send_single_request
    response = await transport.handle_async_request(request)
/usr/local/lib/python3.10/site-packages/httpx/_transports/default.py:353: in handle_async_request
    resp = await self._pool.handle_async_request(req)
/usr/local/lib/python3.10/site-packages/respx/mocks.py:188: in amock
    response = await cls._send_async_request(
/usr/local/lib/python3.10/site-packages/respx/mocks.py:222: in _send_async_request
    httpx_response = await cls.async_handler(httpx_request)
/usr/local/lib/python3.10/site-packages/respx/mocks.py:134: in async_handler
    raise assertion_error
/usr/local/lib/python3.10/site-packages/respx/mocks.py:127: in async_handler
    httpx_response = await router.async_handler(httpx_request)
/usr/local/lib/python3.10/site-packages/respx/router.py:319: in async_handler
    resolved = await self.aresolve(request)
/usr/local/lib/python3.10/site-packages/respx/router.py:292: in aresolve
    with self.resolver(request) as resolved:
/usr/local/lib/python3.10/contextlib.py:142: in __exit__
    next(self.gen)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <respx.router.MockRouter object at 0x7f87b1b2b9a0>
request = <Request('GET', 'http://test.com/id')>

    @contextmanager
    def resolver(self, request: httpx.Request) -> Generator[ResolvedRoute, None, None]:
        resolved = ResolvedRoute()

        try:
            yield resolved

            if resolved.route is None:
                # Assert we always get a route match, if check is enabled
                if self._assert_all_mocked:
>                   raise AllMockedAssertionError(f"RESPX: {request!r} not mocked!")
E                   respx.models.AllMockedAssertionError: RESPX: <Request('GET', 'http://test.com/id')> not mocked!

/usr/local/lib/python3.10/site-packages/respx/router.py:251: AllMockedAssertionError
________________________ test_httpx_all_mocked_false _________________________

    @respx.mock(assert_all_mocked=False)
    async def test_httpx_all_mocked_false():

        async with httpx.AsyncClient() as client:
            request = respx.get("http://test.com/id").mock(
                return_value=httpx.Response(200, content="test")
            )
            resp = await client.get("http://test.com/id")
>           assert resp.content == b"test"
E           AssertionError: assert b'' == b'test'
E             Full diff:
E             - b'test'
E             + b''

tests/routes/test_twitch_users_routes.py:31: AssertionError
========================== short test summary info ===========================
FAILED tests/routes/test_twitch_users_routes.py::test_httpx_all_mocked_true
FAILED tests/routes/test_twitch_users_routes.py::test_httpx_all_mocked_false
======================== 2 failed, 1 passed in 0.53s =========================

Shouldn't all of these tests pass if the tests are identical and rely on the mock?
Also, I see that the default assert_all_mocked value is True. Why is it that the behavior of not passing in a value is different from passing in True?

Furthermore, it appears passing in anything to the respx.mock decorator changes behavior:

e.g.

@respx.mock(base_url="http://example.com")
async def test_with_base_url():
    async with httpx.AsyncClient() as client:
        respx.get("/user").mock(return_value=httpx.Response(200, content="test"))
        resp = await client.get("http://example.com/user")
        assert resp.content == b"test"


@respx.mock
async def test_without_base_url():
    async with httpx.AsyncClient() as client:
        respx.get("http://example.com/user").mock(
            return_value=httpx.Response(200, content="test")
        )
        resp = await client.get("http://example.com/user")
        assert resp.content == b"test"

Results in:

================================== FAILURES ==================================
_____________________________ test_with_base_url _____________________________

    @respx.mock(base_url="http://example.com")
    async def test_with_base_url():
        async with httpx.AsyncClient() as client:
            respx.get("/user").mock(return_value=httpx.Response(200, content="test"))
>           resp = await client.get("http://example.com/user")

tests/routes/test_twitch_users_routes.py:9:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1729: in get
    return await self.request(
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1506: in request
    return await self.send(request, auth=auth, follow_redirects=follow_redirects)
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1593: in send
    response = await self._send_handling_auth(
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1621: in _send_handling_auth
    response = await self._send_handling_redirects(
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1658: in _send_handling_redirects
    response = await self._send_single_request(request)
/usr/local/lib/python3.10/site-packages/httpx/_client.py:1695: in _send_single_request
    response = await transport.handle_async_request(request)
/usr/local/lib/python3.10/site-packages/httpx/_transports/default.py:353: in handle_async_request
    resp = await self._pool.handle_async_request(req)
/usr/local/lib/python3.10/site-packages/respx/mocks.py:188: in amock
    response = await cls._send_async_request(
/usr/local/lib/python3.10/site-packages/respx/mocks.py:222: in _send_async_request
    httpx_response = await cls.async_handler(httpx_request)
/usr/local/lib/python3.10/site-packages/respx/mocks.py:134: in async_handler
    raise assertion_error
/usr/local/lib/python3.10/site-packages/respx/mocks.py:127: in async_handler
    httpx_response = await router.async_handler(httpx_request)
/usr/local/lib/python3.10/site-packages/respx/router.py:319: in async_handler
    resolved = await self.aresolve(request)
/usr/local/lib/python3.10/site-packages/respx/router.py:292: in aresolve
    with self.resolver(request) as resolved:
/usr/local/lib/python3.10/contextlib.py:142: in __exit__
    next(self.gen)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <respx.router.MockRouter object at 0x7fb4d9417d30>
request = <Request('GET', 'http://example.com/user')>

    @contextmanager
    def resolver(self, request: httpx.Request) -> Generator[ResolvedRoute, None, None]:
        resolved = ResolvedRoute()

        try:
            yield resolved

            if resolved.route is None:
                # Assert we always get a route match, if check is enabled
                if self._assert_all_mocked:
>                   raise AllMockedAssertionError(f"RESPX: {request!r} not mocked!")
E                   respx.models.AllMockedAssertionError: RESPX: <Request('GET', 'http://example.com/user')> not mocked!

/usr/local/lib/python3.10/site-packages/respx/router.py:251: AllMockedAssertionError
========================== short test summary info ===========================
FAILED tests/routes/test_twitch_users_routes.py::test_with_base_url - respx...
======================== 1 failed, 1 passed in 0.44s =========================```

Furthermore, it appears passing in anything to the respx.mock decorator changes behavior

Yes, that's correct 😉. Short answer; passing args to respx.mock yields a respx_mock router/arg to your test function.

This is explained here in the docs, also answered in #196 and #197.