Search code examples
pythonpytestmonkeypatching

monkeypatch a function that needs to be imported in conftest


I'm trying to use pytest.monkeypatch to patch a function I've defined in another file. I then need to patch a function from another that relies on this first monkeypatch. Here's a simple example

# class_def.py
class C:
    def __init__(self):
        # Normally, there is something that makes self.p
        # that will use a file that will exist on production
        raise FileNotFoundError

def factory():
    print('in factory')
    return C()

----
# function_def.py
from .class_def import factory

foo = factory()

def bar():
    return 0

----
# conftest.py
from unittest.mock import MagicMock

import pytest

import playground.class_def

@pytest.fixture(autouse=True)
def patch_c(monkeypatch):
    fake_c = MagicMock()
    def factory():
        print('in monkey factory')
        return fake_c
    monkeypatch.setattr('playground.class_def.factory', factory)

from .function_def import bar

# Then I would patch bar

And running pytest . will fail with FileNotFoundError. I believe this happens because I am calling foo = factory() at the top level of function_def.py. I expected this not to happen because I am patching factory before doing this import, but that doesn't seem to be happening. Is there a way to ensure this monkeypatch.setattr will go into effect before from .function_def import bar in conftest.py?

Also, the file structure looks like

playground
|--- __init__.py
|--- conftest.py
|--- class_def.py
|--- function_def

Solution

  • You have direct access to the attribute you want to change. You don't need monkeypatch at all.

    Here's my tree :

    $ tree .
    .
    ├── a.py
    ├── b.py
    ├── __init__.py
    └── test_a.py
    
    0 directories, 4 files
    

    a.py

    class A:
        def __init__(self):
            raise Exception
    
    def factory():
        return A()
    

    b.py

    import a
    
    print(a.factory())
    

    test_a.py

    import a
    
    
    def test_a():
        def fake_factory():
            return 'A'
        a.factory = fake_factory
        import b
    

    And it works:

    $ pytest
    =============================================================================================== test session starts ===============================================================================================
    platform linux -- Python 3.6.5, pytest-3.5.1, py-1.5.3, pluggy-0.6.0
    rootdir: /home/ahorgnies/test/monkeypatch, inifile:
    plugins: remotedata-0.2.1, openfiles-0.3.0, doctestplus-0.1.3, arraydiff-0.2
    collected 1 item                                                                                                                                                                                                  
    
    test_a.py .                                                                                                                                                                                                 [100%]
    
    ============================================================================================ 1 passed in 0.01 seconds =============================================================================================