pytest-mock icon indicating copy to clipboard operation
pytest-mock copied to clipboard

Allow use of custom mock implementations

Open antdking opened this issue 6 years ago • 7 comments

With the growing use of asyncio, we need ways to mock Coroutines.

Currently, mock and unittest.mock do not support creating specs for coroutines (AsyncMock is being added in python 3.8).

As a work around, we're using asynctest.mock, which has the same interface as unittest.mock, however this requires us to completely re-implement the __init__ method of MockFixture, and removes the "hide traceback" functionality (though we don't care too much about this). This allows us to have both mocker and async_mocker fixtures.

TLDR To ease this usecase (and potentially others), Can support for defining a mock modules path from the config be added please?

antdking avatar Jun 27 '19 08:06 antdking

Please note: asynctest.mock.CoroutineMock is not compatible with unittest.mock.AsyncMock. It is not a drop-in replacement. Moreover, AsyncMock doesn't exist on it's own, it is deeply integrated with other unittest.mock objects and functions. Thus, the whole unittest.mock should be replaced with asynctest.mock with even more incompatibility level.

I rather suggest not touching pytest-mock library but wait for 3.8. Similar but slightly different API makes more harm than help. The support of such solutions is hard in long term perspective.

If you really need asynctest.mock.CoroutineMock support you can clone pytest-mock, add needed hacks, and use it on your own.

asvetlov avatar Jun 27 '19 12:06 asvetlov

I didn't take a look at the problem in detail, but @asvetlov makes a point about supporting slightly different APIs in the same package brings more problems than it helps.

Perhaps we should create a new pytest-asyncmock plugin instead?

nicoddemus avatar Jun 27 '19 14:06 nicoddemus

agree with your points on this. I think it's probably best to wait until 3.8 for this library.

In terms of creating a new plugin, it might be better to try and get the fixtures into the asynctest library instead. Will close this issue for now.

The hack we have in place works well enough for us until that time, and for our specific usecase, it will be an easy migration.

If anyone else stumbles on this issue, here is the hack:

import pytest

import pytest_mock
import asynctest.mock


@pytest.fixture
def async_mocker(pytestconfig):
    # This is a straight copy + paste from pytest_mock, but with our patched MockFixture
    result = AsyncMockFixture(pytestconfig)
    yield result
    result.stopall()


class AsyncMockFixture(pytest_mock.MockFixture):
    def __init__(self, config):
        # This is a straight copy + paste from pytest_mock
        # TODO: contribute a way to use arbitary mock libraries upstream

        self._patches = []  # list of mock._patch objects
        self._mocks = []  # list of MagicMock objects

        # CHANGED: hard coding the asynctest.mock
        self.mock_module = mock_module = asynctest.mock

        self.patch = self._Patcher(self._patches, self._mocks, mock_module)
        # aliases for convenience
        self.Mock = mock_module.Mock
        self.MagicMock = mock_module.MagicMock
        self.NonCallableMock = mock_module.NonCallableMock
        self.PropertyMock = mock_module.PropertyMock
        self.call = mock_module.call
        self.ANY = mock_module.ANY
        self.DEFAULT = mock_module.DEFAULT
        self.create_autospec = mock_module.create_autospec
        self.sentinel = mock_module.sentinel
        self.mock_open = mock_module.mock_open

        # CoroutineMock is from asynctest
        # AsyncMock is being added in python 3.8
        # Please use AsyncMock.
        self.CoroutineMock = self.AsyncMock = mock_module.CoroutineMock

antdking avatar Jun 28 '19 08:06 antdking

Python 3.8 is out. It might be worth reopening this issue as there's AsyncMock available now.

Jackenmen avatar Oct 24 '19 15:10 Jackenmen

Thanks for the ping @jack1142.

Will gladly accept PRs in this regard if all it is required is to provide a custom mock implementation, but I probably would not like to include it into pytest-mock it this would require an entire new implementation of pytest_mock.MockFixture.

nicoddemus avatar Oct 24 '19 15:10 nicoddemus

~~mock since version 4.0.0 has the AsyncMock class which could be exposed directly through mocker.AsyncMock (like the usual mocker.Mock)~~

EDIT: Sorry I thought pytest-mock had a dependency on mock, but this is not the case so it's not as easy as I thought.

ThibaultLemaire avatar May 15 '20 09:05 ThibaultLemaire

FWIW, AsyncMock is now available as part of mocker from https://github.com/pytest-dev/pytest-mock/commit/449d3d038e076fcdbb860815d306d4a59db44141 with rich assert diff for assert helpers associated with AsyncMock

tirkarthi avatar Jul 26 '20 05:07 tirkarthi