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

Send a mocked request more than once then got ClientConnectionError #164

Open
leftvalue opened this issue Jun 25, 2020 · 8 comments
Open

Comments

@leftvalue
Copy link

leftvalue commented Jun 25, 2020

when i send a mocked request more than once, got ../../venv/lib/python3.7/site-packages/aioresponses/core.py:392: ClientConnectionError

# -*- coding: utf-8 -*-
import aiohttp
import pytest
from aioresponses import CallbackResult, aioresponses


def callback(url, **kwargs):
    return CallbackResult(status=418)


@pytest.mark.asyncio
async def test_callback(aiohttp_client):
    with aioresponses() as m:
        session = aiohttp.ClientSession()
        m.get('http://example.com', callback=callback)
        for i in range(2): # 1 is ok,2 cannot pass
            resp = await session.get('http://example.com')
            assert resp.status == 418

the error is

self = <aioresponses.core.aioresponses object at 0x10b4360b8>
orig_self = <aiohttp.client.ClientSession object at 0x10b4435f8>, method = 'GET'
url = URL('http://example.com'), args = (), kwargs = {'allow_redirects': True}
url_origin = 'http://example.com', url_str = 'http://example.com'
key = ('GET', URL('http://example.com'))
kwargs_copy = {'allow_redirects': True}, response = None

    async def _request_mock(self, orig_self: ClientSession,
                            method: str, url: 'Union[URL, str]',
                            *args: Tuple,
                            **kwargs: Dict) -> 'ClientResponse':
        """Return mocked response object or raise connection error."""
        if orig_self.closed:
            raise RuntimeError('Session is closed')
    
        url_origin = url
        url = normalize_url(merge_params(url, kwargs.get('params')))
        url_str = str(url)
        for prefix in self._passthrough:
            if url_str.startswith(prefix):
                return (await self.patcher.temp_original(
                    orig_self, method, url_origin, *args, **kwargs
                ))
    
        key = (method, url)
        self.requests.setdefault(key, [])
        try:
            kwargs_copy = copy.deepcopy(kwargs)
        except TypeError:
            # Handle the fact that some values cannot be deep copied
            kwargs_copy = kwargs
        self.requests[key].append(RequestCall(args, kwargs_copy))
    
        response = await self.match(method, url, **kwargs)
    
        if response is None:
            raise ClientConnectionError(
>               'Connection refused: {} {}'.format(method, url)
            )
E           aiohttp.client_exceptions.ClientConnectionError: Connection refused: GET http://example.com

../../venv/lib/python3.7/site-packages/aioresponses/core.py:392: ClientConnectionError

Assertion failed

wonder if anyone has see the problem before, or perhaps my usage is somewhere bad?

@leftvalue
Copy link
Author

i have found that if i

m.get('http://example.com', callback=callback)
        m.get('http://example.com', callback=callback)
        m.get('http://example.com', callback=callback)
        for i in range(2):
            resp = await session.get('http://example.com')
            assert resp.status == 418

then resolved the problem
why does it make sence?
are there any better solutions?

@internetofjames
Copy link

@leftvalue I was having this issue too. I'm currently switching a project over from the synchronous requests library. It looks like, unlike the popular requests-mock library for mocking out calls by requests, this library currently does not default to indefinitely using the last mocked response. I found this class attribute used when mocking the call. It's not at all documented, but changing your test like this for the mock you wish to reuse should work:

@pytest.mark.asyncio
async def test_callback(aiohttp_client):
    with aioresponses() as m:
        session = aiohttp.ClientSession()
        m.get('http://example.com', callback=callback, repeat=True)
        for i in range(2): # 1 is ok,2 cannot pass
            resp = await session.get('http://example.com')
            assert resp.status == 418

where you add the kwarg repeat=True to the get request mock.

@pnuckowski
Copy link
Owner

@leftvalue Hi,
that is expected behavior, to keep mocked url use repeat argument ( as @internetofjames suggest) :)

@internetofjames
Copy link

@pnuckowski may I ask, why is this the default behavior? Wouldn't a user want the mock to hold in place until the end of the test, or until they were to explicitly state a passthrough to make a real request? IMO this current default behavior could lead to tests making unintended requests to real infrastructure. I'd be happy to take a shot at it and make a PR if you agree.

Otherwise it would be of great help to specify the repeat keyword argument in the documentation to assist others who are likely to encounter this same issue as the thrown exception is not that helpful in deducing this behavior.

@pnuckowski
Copy link
Owner

@internetofjames In all of my projects I rarely wanted a mock that holds all/specific request and don"t feel it should be changed.
Same behavior is in responses package (not sure how it's done in other one, but pretty sure quite similar).

To hit real infrastructure developer has to define which url should be passed (passthrough argument), so there is no option to hit infrastructure by mistake using default configuration.

According to documentation, indeed there was few PR with improvements and new features and doc are lack of mentioning of them. This is the task at the top of my list to improve :)

I'm wondering how to structure documentation, so If you read (lately) really good written docs, please let me know :) it might help me to make it right:)

@internetofjames
Copy link

@pnuckowski some docs that came to mind were for the requests library: https://requests.readthedocs.io/en/master/

@RafaelWO
Copy link

Adding docs would help people find this "hidden" argument :)

@oren0e
Copy link
Contributor

oren0e commented May 22, 2022

@pnuckowski I've made a pull request adding the missing repeat argument to the documentation examples in the readme.

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

No branches or pull requests

5 participants