question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to test httpx with trio and respx

See original GitHub issue

Hi Lundberg, I am working on a project on httpx using trio async libray. Following your documentation, I ended up with a test like this:

    @pytest.mark.trio
    async def test_should_do_something(trio_tmp_path):
        async with respx.mock:
            async with httpx.AsyncClient() as client:
                analyser = RobotsAnalyzer(
                    user_agent='Mozilla/5.0',
                    robots_cache=trio_tmp_path,
                    http_client=client,
                    robots_mapping_lock=robots_mapping_lock_mock,
                    delay_mapping_lock=delay_mapping_lock_mock
                )
                respx.get('/robots.txt', content=httpx.ConnectTimeout())
                assert await analyser.can_fetch('http://example.com/path') is False

but I have this weird error, I don’t know if there is a tip to avoid the issue.

value = <trio.Nursery object at 0x051E4EB0>

    async def yield_(value=None):
>       return await _yield_(value)

C:\Users\rolla\AppData\Local\pypoetry\Cache\virtualenvs\scalpel-HJDWBcSE-py3.8\lib\site-packages\async_generator\_impl.py:106: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
C:\Users\rolla\AppData\Local\pypoetry\Cache\virtualenvs\scalpel-HJDWBcSE-py3.8\lib\site-packages\async_generator\_impl.py:99: in _yield_
    return (yield _wrap(value))
test_robots.py:104: in test_should_return_false_when_host_does_not_exist
    assert await analyser.can_fetch('http://example.com/path') is False
..\..\scalpel\trionic\robots.py:53: in can_fetch
    response = await self._http_client.get(f'{robots_url}')
C:\Users\rolla\AppData\Local\pypoetry\Cache\virtualenvs\scalpel-HJDWBcSE-py3.8\lib\site-packages\httpx\_client.py:1224: in get
    return await self.request(
C:\Users\rolla\AppData\Local\pypoetry\Cache\virtualenvs\scalpel-HJDWBcSE-py3.8\lib\site-packages\httpx\_client.py:1085: in request
    response = await self.send(
C:\Users\rolla\AppData\Local\pypoetry\Cache\virtualenvs\scalpel-HJDWBcSE-py3.8\lib\site-packages\respx\mocks.py:162: in unbound_async_send
    return await self.__AsyncClient__send__spy(client, request, **kwargs)
C:\Users\rolla\AppData\Local\pypoetry\Cache\virtualenvs\scalpel-HJDWBcSE-py3.8\lib\site-packages\respx\mocks.py:530: in __AsyncClient__send__spy
    with self._patch_dispatcher(client.dispatch, request) as capture:
C:\Users\rolla\AppData\Local\Programs\Python\Python38-32\lib\contextlib.py:113: in __enter__
    return next(self.gen)
C:\Users\rolla\AppData\Local\pypoetry\Cache\virtualenvs\scalpel-HJDWBcSE-py3.8\lib\site-packages\respx\mocks.py:472: in _patch_dispatcher
    httpx_mock, pattern, response = self._match(request)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

self = <respx.mocks.HTTPXMock object at 0x051363E8>
request = <Request('GET', 'http://example.com/robots.txt')>

    def _match(
        self, request: Request
    ) -> typing.Tuple[
        "HTTPXMock", typing.Optional[RequestPattern], typing.Optional[ResponseTemplate]
    ]:
        used_mock = self
        matched_pattern: typing.Optional[RequestPattern] = None
        matched_pattern_index: typing.Optional[int] = None
        response: typing.Optional[ResponseTemplate] = None
    
        # Iterate all started mockers and their patterns
        for httpx_mock in self._mocks:
            patterns = httpx_mock._patterns
    
            for i, pattern in enumerate(patterns):
                match = pattern.match(request)
                if not match:
                    continue
    
                if matched_pattern_index is not None:
                    # Multiple matches found, drop and use the first one
                    patterns.pop(matched_pattern_index)
                    break
    
                used_mock = httpx_mock
                matched_pattern = pattern
                matched_pattern_index = i
    
                if isinstance(match, ResponseTemplate):
                    # Mock response
                    response = match
                elif isinstance(match, Request):
                    # Pass-through request
                    response = None
                else:
                    raise ValueError(
                        (
                            "Matched request pattern must return either a "
                            'ResponseTemplate or a Request, got "{}"'
                        ).format(type(match))
                    )
    
            if matched_pattern:
                break
    
        if matched_pattern is None:
            # Assert we always get a pattern match, if check is enabled
            allows_unmocked = tuple(m for m in self._mocks if not m._assert_all_mocked)
>           assert allows_unmocked, f"RESPX: {request!r} not mocked!"
E           AssertionError: RESPX: <Request('GET', 'http://example.com/robots.txt')> not mocked!

My environment

  • windows 10
  • python3.8
  • trio 0.13.0
  • pytest-trio 0.5.2
  • respx 0.10.1

Best regards

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:5 (5 by maintainers)

github_iconTop GitHub Comments

1reaction
lewoudarcommented, May 30, 2020

Hi lundberg, thank you for the tip. I don’t really know where I made a mistake but re-using your last example works for me.

Again thanks 😃

0reactions
lundbergcommented, May 29, 2020

I re-read your issue and I think I was to fast writing my last answer 😉

When configuring the respx mock object with base_url we need to add the request patterns to the context manager instance.

Try this:

async with respx.mock(base_url="http://example.com") as respx_mock:
    ...
    respx_mock.get('/robots.txt', content=httpx.ConnectTimeout())  # NOTE: mock instance
    ....

…hopes this was the issue and helps you.

Read more comments on GitHub >

github_iconTop Results From Across the Web

User Guide - RESPX
To patch HTTPX , and activate the RESPX router, use the respx.mock ... when using the pytest fixture, decorate the test case with...
Read more >
python-trio/general - Gitter
how do you properly develop unit tests for code that calls an external REST API? sorry if this is a bit of a...
Read more >
trio + httpx gives TrioDeprecationWarning - Stack Overflow
I raised this in https://github.com/encode/httpx/discussions/2409. To silence the warning: import warnings from trio import ...
Read more >
Setting up Python Projects: Part III | by Johannes Schmidt
Because we use httpx, we need to mock the response from the HTTP call (https://pokeapi.co/api). ... poetry add --group test respx pytest-asyncio trio....
Read more >
pytest-trio 0.7.0 - PythonFix.com
Pytest plugin for trio. ... GitHub Repo: https://github.com/python-trio/pytest-trio. Classifiers. Software Development/Testing; System/Networking ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found