3

Does pytest provides functionality like unittest.mock to check if the mock was actually called once(or once with some parameter)?

Sample Source code:

my_package/my_module.py

from com.abc.validation import Validation


class MyModule:
    def __init__(self):
        pass

    def will_call_other_package(self):
        val = Validation()
        val.do()

    def run(self):
        self.will_call_other_package()

Sample test code for the above source code:

test_my_module.py

import pytest
from pytest_mock import mocker

from my_package.my_module import MyModule

@pytest.fixture
def mock_will_call_other_package(mocker):
    mocker.patch('my_package.my_module.will_call_other_package')


@pytest.mark.usefixtures("mock_will_call_other_package")
class TestMyModule:

    def test_run(self):
        MyModule().run()
        #check `will_call_other_package` method is called.

        #Looking for something similar to what unittest.mock provide
        #mock_will_call_other_package.called_once

Sandesh Wani
  • 39
  • 1
  • 8
  • You can just use `unittest.mock` with `pytest` as usually. There is also the `pytest-mock` plugin that provides the `mocker` fixture, which is basically a thin wrapper around `unittest.mock`. – MrBean Bremen Apr 23 '21 at 16:01
  • If I am using `unittest.mock` it kills the whole purpose of why I started using fixtures in first place (to re-use mocks) Can you show me an example of how this could be achieved using `pytest-mock` ? – Sandesh Wani Apr 23 '21 at 19:57
  • You can put the mock into a fixture. I can show you an example tomorrow, not at a computer right now. – MrBean Bremen Apr 23 '21 at 20:15

2 Answers2

5

If you want to use a fixture that does the patching, you can move the patching into a fixture:

import pytest
from unittest import mock

from my_package.my_module import MyModule

@pytest.fixture
def mock_will_call_other_package():
    with mock.patch('my_package.my_module.will_call_other_package') as mocked:
        yield mocked
    # the mocking will be reverted here, e.g. after the test


class TestMyModule:

    def test_run(self, mock_will_call_other_package):
        MyModule().run()
        mock_will_call_other_package.assert_called_once()

Note that you have to use the fixture parameter in the test. Just using @pytest.mark.usefixtures will not give you access to the mock itself. You can still use it to be effective in all tests in the class, if you don't need to access the mock in all tests (or use autouse=True in the fixture).

Also note that you don't need pytest-mock here - but as mentioned by @hoefling, using it makes the fixture better readable, because you don't need the with clause :

@pytest.fixture
def mock_will_call_other_package(mocker):
    yield mocker.patch('my_package.my_module.will_call_other_package')

As an aside: you don't need to import mocker. Fixtures are looked up by name, and available automatically if the respective plugin is installed.

MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46
1

First of all, you may not need the burden of an external library such as pytest_mock, because pytest already got you covered using the integration with unittest.

You also do not need to use the usefixtures because whenever you need a fixture, you just receive it in your test method.

An ideal scenario based on your own code would look similar to this:

import pytest
from unittest.mock import patch

from com.abc.validation import Validation


class MyModule:
    def __init__(self):
        pass

    def will_call_other_package(self):
        val = Validation()
        val.do()

    def run(self):
        self.will_call_other_package()


@pytest.fixture
def call_other_module():
    with patch("my_package.my_module.MyModule.will_call_other_package") as _patched:
        yield _patched


class TestMyModule:
    def test_run_will_call_other_package(self, call_other_module):
        call_other_module.assert_not_called()
        obj = MyModule()
        call_other_module.assert_not_called()
        obj.run()
        call_other_module.assert_called_once()

And also if you want to make sure that you did infact patch the target MyModule.will_call_other_package, modify your test like this:

class TestMyModule:
    def test_run_will_call_other_package(self, call_other_module):
        call_other_module.assert_not_called()
        obj = MyModule()
        call_other_module.assert_not_called()
        obj.run()
        call_other_module.assert_called_once()
        assert False, (MyModule.will_call_other_package, call_other_module)

And you'll see something similar to this:

AssertionError: (<MagicMock name='will_call_other_package' id='140695551841328'>, <MagicMock name='will_call_other_package' id='140695551841328'>)

As you can see the id of both objects are the same, confirming our experiment was successful.

Meysam
  • 596
  • 6
  • 12