3

I am following this mini-tutorial/blog on pytest-mock. I can not understand how the mocker is working since there is no import for it - in particular the function declaration def test_mocking_constant_a(mocker):

import mock_examples.functions
from mock_examples.functions import double

def test_mocking_constant_a(mocker):
    mocker.patch.object(mock_examples.functions, 'CONSTANT_A', 2)
    expected = 4  
    actual = double()  # now it returns 4, not 2

    assert expected == actual

Somehow the mocker has the attributes/functions of pytest-mocker.mocker: in particular mocker.patch.object . But how can that be without the import statement?

Mike Pennington
  • 41,899
  • 19
  • 136
  • 174
WestCoastProjects
  • 58,982
  • 91
  • 316
  • 560
  • 3
    `mocker` is a fixture [defined in the plugin](https://docs.pytest.org/en/latest/reference/fixtures.html#fixtures-from-third-party-plugins). These fixtures are loaded at pytest startup and looked up by name, as all fixtures are. – MrBean Bremen Feb 18 '22 at 08:04
  • @MrBeanBremen. pls make that an answer it's a helpful link and summary – WestCoastProjects Feb 18 '22 at 08:08

1 Answers1

4

The mocker variable is a Pytest fixture. Rather than using imports, fixtures are supplied using dependency injection - that is, Pytest takes care of creating the mocker object for you and supplies it to the test function when it runs the test.

Pytest-mock defines the "mocker" fixture here, using the Pytest fixture decorator. Here, the fixture decorator is used as a regular function, which is a slightly unusual way of doing it. A more typical way of using the fixture decorator would look something like this:

@pytest.fixture()
def mocker(pytestconfig: Any) -> Generator[MockerFixture, None, None]:
    """
    Return an object that has the same interface to the `mock` module, but
    takes care of automatically undoing all patches after each test method.
    """
    result = MockerFixture(pytestconfig)
    yield result
    result.stopall()

The fixture decorator registers the "mocker" function with Pytest, and when Pytest runs a test with a parameter called "mocker", it inserts the result of the "mocker" function for you.

Pytest can do this because it uses Python's introspection features to view the list of arguments, complete with names, before calling the test function. It compares the names of the arguments with names of fixtures that have been registered, and if the names match, it supplies the corresponding object to that parameter of the test function.

Jack Taylor
  • 5,588
  • 19
  • 35
  • 1
    it's past midnight and i'm too tired to consume this beverage now. But i'm assuming it makes sense to someone more presently coherent and awarding – WestCoastProjects Feb 18 '22 at 08:11
  • 2
    It totally does. If you are curious about it, it uses the introspection features of python, to inspect the test function before calling it. This allows pytest to get the list of required arguments - with names - and look up the name in the dict of loaded fixtures. – spectras Feb 18 '22 at 08:14
  • 1
    @spectras Thanks for the clarification. I added a paragraph about name introspection to my answer. – Jack Taylor Feb 18 '22 at 08:27
  • I was a big google guice fan/user . Did not know python even supports DI – WestCoastProjects Feb 18 '22 at 12:37