Search code examples
pythonpytestfixtures

(pytest) Why doesn't property mock work in fixture?


I have a class with some properties. In my test, I need to set up a fixture, and have the properties mocked. However, the patch only works in the fixture function, not when the fixture is called. Any idea how to fix this?

Here is the simplified version of the problem. Let's assume that this is my class Panda:

class Panda(object):
    def __init__(self, name):
        self.panda_name = name

    @property
    def name(self):
        return self.panda_name

and this is my test

import pytest
from unittest.mock import patch, PropertyMock
from tmp import Panda


@pytest.fixture
@patch(
    'tmp.Panda.name',
    new_callable=PropertyMock,
    return_value="yuanyuan")
def fixture_panda(mk_name):
    p = Panda("this name should not matter")
    print(p.name)  # print "yuanyuan"
    return p


def test_panda_fixture(fixture_panda):
    p = fixture_panda
    print(p.name)  # print "this name should not matter"
    # this test fails
    assert p.name == "yuanyuan"

The first print function in fixture_panda would print yuanyuan, which means the propertyMock works as expected. However the 2nd print function in test_panda_fixture print this name should not matter, which means the propertyMock doesn't work here. Any idea why this happens and how to fix this?


Solution

  • If you want to monkeypatch something in pytest, you can use their built-in fixture monkeypatch, which can be inserted into all fixtures with scope = function. Here is an example from my codebase:

    @pytest.fixture(scope="function", autouse=True)
    def no_jwt(monkeypatch):
      """Monkeypatch the JWT verification functions for tests"""
      monkeypatch.setattr("flask_jwt_extended.verify_jwt_in_request", lambda: print("Verify"))
    

    If I apply it to your example, I think something like this should work:

    @pytest.fixture
    def fixture_panda(monkeypatch, mk_name):
      monkeypatch.setattr('tmp.Panda.name', "yuanyuan")
      p = Panda("this name should not matter")
      print(p.name)  # print "yuanyuan"
      return p