3

I want to mock the json() coroutine from the aiohttp.ClientSession.get method. It looks to return an async generator object, which is where I'm confused on how to mock in my example. Here is my code:

async def get_access_token():

async with aiohttp.ClientSession(auth=auth_credentials) as client:
    async with client.get(auth_path, params={'grant_type': 'client_credentials'}) as auth_response:
        assert auth_response.status == 200
        auth_json = await auth_response.json()
        return auth_json['access_token']

This is my test case to mock the get method:

json_data = [{
'access_token': 'HSG9hsf328bJSWO82sl',
'expires_in': 86399, 
'token_type': 'bearer'
}]

class AsyncMock:
    async def __aenter__(self):
        return self

    async def __aexit__(self, *error_info):
        return self

@pytest.mark.asyncio
async def test_wow_api_invalid_credentials(monkeypatch, mocker):
    def mock_client_get(self, auth_path, params):
        mock_response = AsyncMock()
        mock_response.status = 200
        mock_response.json = mocker.MagicMock(return_value=json_data)
        return mock_response

    monkeypatch.setattr('wow.aiohttp.ClientSession.get', mock_client_get)
    result = await wow.get_access_token()

assert result == 'HSG9hsf328bJSWO82sl'

I think the problem might be that mock_response.json() is not awaitable. In my example I can't call await from a non async function so I'm confused on how I would do that. I would like to keep the test libraries to a minimum which is pytest and pytest-asyncio for the learning experiencing and to rely less on 3rd party libraries.

braaterAfrikaaner
  • 1,072
  • 10
  • 20
mroth7684
  • 103
  • 8
  • Why not just use monkeypatch to assign your own coroutine? – dirn Dec 29 '18 at 04:20
  • @dirn Where the current monkeypatch is placed in my code it expects a non async object with status function. I was able to mock up to that point. Now it expects the object to have a json coroutine. In my example I mocked it as a regular function because I'm not sure how to change the code to await the json coroutine since I can't await it from where I define the mock_client_get method monkeypatch. Would I make a second monkeypatch to patch the json coroutine in mock_client_get with my own json coroutine? – mroth7684 Dec 29 '18 at 14:28

2 Answers2

1

I was making it more complicated than it needed to be. I simply defined json as an awaitable attribute of AsyncMock which returns the json_data. The complete code looks like this:

json_data = {
'access_token': 'HSG9hsf328bJSWO82sl',
'expires_in': 86399, 
'token_type': 'bearer'
}

 class AsyncMock:
    async def __aenter__(self):
        return self

    async def __aexit__(self, *error_info):
        return self

    async def json(self):
        return json_data

@pytest.mark.asyncio
async def test_wow_api_invalid_credentials(monkeypatch):
    def mock_client_get(self, auth_path, params):
        mock_response = AsyncMock()
        mock_response.status = 200
        return mock_response

    monkeypatch.setattr('wow.aiohttp.ClientSession.get', mock_client_get)
    result = await wow.get_access_token()

    assert result == 'HSG9hsf328bJSWO82sl'
mroth7684
  • 103
  • 8
0

This is part 1, but i suggest you watch part2.

Im not sure i understand your question totally, because using async def or @asyncio.coroutine can help you do this. Actually, i want to write it as comment, however there are so many differences that i can't put it into comment.

import asyncio

json_ = [{
'access_token': 'HSG9hsf328bJSWO82sl',
'expires_in': 86399, 
'token_type': 'bearer'
}]


async def response_from_sun():
    return json_


class AsyncMock:

    async def specify(self):
        return self.json[0].get("access_token")

    async def __aenter__(self):
        return self

    async def __aexit__(self, *error_info):
        return self

async def mock_client_get():
    mock_response = AsyncMock()
    mock_response.status = 200
    mock_response.json = await response_from_sun()
    return mock_response

async def go():
    resp = await mock_client_get()
    result = await resp.specify()
    assert result == 'HSG9hsf328bJSWO82sl'

asyncio.get_event_loop().run_until_complete(go())

PART2

After adding my answer, i found there is a problem about your mock_response content. Becausemock_response does not contain variable and function which ClientResponse have.

Edit: I try many times and watch ClientSession's code, then i found you can specify a new response class by its parameter. Note: connector=aiohttp.TCPConnector(verify_ssl=False) is unnecessary

import asyncio
import aiohttp

class Mock(aiohttp.ClientResponse):
    print("Mock")

    async def specify(self):
        json_ = (await self.json()).get("hello")
        return json_

async def go():
    async with aiohttp.ClientSession(connector=aiohttp.TCPConnector(verify_ssl=False),response_class=Mock) as session:
        resp = await session.get("https://www.mocky.io/v2/5185415ba171ea3a00704eed")
        result = await resp.specify()
    print(result)
    assert result == 'world'

asyncio.get_event_loop().run_until_complete(go())
KC.
  • 2,981
  • 2
  • 12
  • 22