5

src/mainDir/mainFile.py

contents of mainFile.py

import src.tempDir.tempFile as temp

data = 'someData'
def foo(self):
    ans = temp.boo(data)
    return ans

src/tempDir/tempFile.py

def boo(data):

   ans = data
   return ans

Now I want to test foo() from src/tests/test_mainFile.py and I want to mock temp.boo(data) method in foo() method

 import src.mainDir.mainFile as mainFunc

  testData = 'testData'
  def test_foo(monkeypatch):
     monkeypatch.setattr('src.tempDir.tempFile', 'boo', testData)
     ans = mainFunc.foo()
     assert ans == testData

but I get error

AttributeError: 'src.tempDir.tempFile' has no attribute 'boo'

I expect ans = testData.

I would like to know if I am correctly mocking my tempDir.boo() method or I should use pytest's mocker instead of monkeypatch.

1
  • 1
    I have the same problem too. Did you get any solution for this? Commented May 7, 2020 at 6:21

3 Answers 3

4

My use case was was slightly different but should still apply. I wanted to patch the value of sys.frozen which is set when running an application bundled by something like Pyinstaller. Otherwise, the attribute does not exist. Looking through the pytest docs, the raising kwarg controls wether or not AttributeError is raised when the attribute does not already exist. (docs)

Usage Example

import sys

def test_frozen_func(monkeypatch):
    monkeypatch.setattr(sys, 'frozen', True, raising=False)
    # can use ('fq_import_path.sys.frozen', ...)
    # if what you are trying to patch is imported in another file
    assert sys.frozen
Sign up to request clarification or add additional context in comments.

Comments

3

You're telling monkeypatch to patch the attribute boo of the string object you pass in.

You'll either need to pass in a module like monkeypatch.setattr(tempFile, 'boo', testData), or pass the attribute as a string too (using the two-argument form), like monkeypatch.setattr('src.tempDir.tempFile.boo', testData).

3 Comments

FYI, the two-argument form for setattr is no longer supported. See docs: docs.pytest.org/en/stable/monkeypatch.html
@KyleKing That's not correct - it's just missing from the docs (it's in the reference docs). See github.com/pytest-dev/pytest/pull/8297
Thanks for clarifying! I updated my answer. I tried the two argument form to test before commenting, but I must not have read the error message carefully
0

Update: mocking function calls can be done with monkeypatch.setattr('package.main.slow_fun', lambda: False) (see answer and comments in https://stackoverflow.com/a/44666743/3219667) and updated snippet below


I don't think this can be done with pytest's monkeypatch, but you can use the pytest-mock package. Docs: https://github.com/pytest-dev/pytest-mock

Quick example with the two files below:

# package/main.py
def slow_fun():
    return True

def main_fun():
    if slow_fun():
        raise RuntimeError('Slow func returned True')
# tests/test_main.py
from package.main import main_fun

# Make sure to install pytest-mock so that the mocker argument is available
def test_main_fun(mocker):
    mocker.patch('package.main.slow_fun', lambda: False)
    main_fun()

# UPDATE: Alternative with monkeypatch
def test_main_fun_monkeypatch(monkeypatch):
    monkeypatch.setattr('package.main.slow_fun', lambda: False)
    main_fun()

Note: this also works if the functions are in different files

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.