2

Im using unittest.mock for building tests for my python code. I have a method that I am trying to test that contains a async call to another function. I want to patch that async call so that I can just have Mock return a testing value for the asset id, and not actually call the async method. I have tried many things I've found online, but none have worked thus far.

Simplified example below:

test.py

import pytest

from app.create.creations import generate_new_asset
from app.fakeapi.utils import create_asset

from unittest.mock import Mock, patch

@patch("app.fakeapi.utils.create_asset")
@pytest.mark.anyio
async def test_generate_new_asset(mock_create):
    mock_create.return_value = 12345678

    await generate_new_asset()

    ...

creations.py

from app.fakeapi.utils import create_asset
...

async def generate_new_asset()
    ...
    # When I run tests this does not return the 12345678 value, but actually calls the `create_asset` method.
    return await create_asset(...) 
red
  • 65
  • 5

1 Answers1

3

Testing async code is bit tricky. If you are using python3.8 or higher AsyncMock is available.

Note: it will work only for Python > 3.8

I think in your case event loop is missing. Here is the code which should work, you may need to do few tweaks. You may also need to install pytest-mock. Having it as fixture will allow you to have mock different values for testing for different scenarios.

import asyncio
from unittest.mock import AsyncMock, Mock

@pytest.fixture(scope="module")
def mock_create_asset(mocker):
   async_mock = AsyncMock()
   mocker.patch('app.fakeapi.utils.create_asset', side_effect=async_mock)
   return async_mock

@pytest.fixture(scope="module")
def event_loop():
    return asyncio.get_event_loop()

 @pytest.mark.asyncio
 async def test_generate_new_asset(mock_create_asset):
    mock_create_asset.return_value = 12345678
    await generate_new_asset() 
Macintosh_89
  • 664
  • 8
  • 24
  • Thank you for your help, I seem to still be having some issues. So I tried the above, and got to the point where I am getting an error about scope, "You tried to access the function scoped fixture mocker with a module scoped request object". So I tried to change the scoping to "function"/the default, but although the test now runs, I am still seeing the internal `create_asset` function being called instead of being mocked/patched. – red Oct 10 '22 at 13:12
  • you may need to mock where `create_asset` is called . did u try patching `import creations` `mocker.patch('creations.create_asset', side_effect=async_mock)` – Macintosh_89 Oct 10 '22 at 13:15
  • Yeah I saw in your example the `.creations` was missing so in my code its `mocker.patch("app.fakeapi.utils.creations.create_asset', side_effect=async_mock")` if thats what you mean. – red Oct 10 '22 at 13:28
  • Yes, does that work for you ? – Macintosh_89 Oct 10 '22 at 13:52
  • No, the `create_asset` method is still actually being called, its not being mocked for the test for some reason. – red Oct 10 '22 at 14:57
  • without seeing whole code , it is difficult to know where is it going wrong – Macintosh_89 Oct 10 '22 at 16:59
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/248706/discussion-between-macintosh-89-and-red). – Macintosh_89 Oct 10 '22 at 17:04
  • In chat we found my issue, it was that I was patching where the method was being defined, not where it was being called – red Oct 10 '22 at 19:06