Python Mocking a function from an imported module
Asked Answered
D

5

230

I want to understand how to @patch a function from an imported module.

This is where I am so far.

app/mocking.py:

from app.my_module import get_user_name

def test_method():
  return get_user_name()

if __name__ == "__main__":
  print "Starting Program..."
  test_method()

app/my_module/__init__.py:

def get_user_name():
  return "Unmocked User"

test/mock-test.py:

import unittest
from app.mocking import test_method 

def mock_get_user():
  return "Mocked This Silly"

@patch('app.my_module.get_user_name')
class MockingTestTestCase(unittest.TestCase):

  def test_mock_stubs(self, mock_method):
    mock_method.return_value = 'Mocked This Silly')
    ret = test_method()
    self.assertEqual(ret, 'Mocked This Silly')

if __name__ == '__main__':
  unittest.main()

This does not work as I would expect. The "patched" module simply returns the unmocked value of get_user_name. How do I mock methods from other packages that I am importing into a namespace under test?

Deglutinate answered 21/4, 2013 at 17:48 Comment(1)
I am asking if I am going about this right. I looked at Mock, but I don't see a way to solve this particular problem. Is there a way to recreate what I did above in Mock?Deglutinate
A
310

When you are using the patch decorator from the unittest.mock package you are patching it in the namespace that is under test (in this case app.mocking.get_user_name), not the namespace the function is imported from (in this case app.my_module.get_user_name).

To do what you describe with @patch try something like the below:

from mock import patch
from app.mocking import test_method 

class MockingTestTestCase(unittest.TestCase):
    
    @patch('app.mocking.get_user_name')
    def test_mock_stubs(self, test_patch):
        test_patch.return_value = 'Mocked This Silly'
        ret = test_method()
        self.assertEqual(ret, 'Mocked This Silly')

The standard library documentation includes a useful section describing this.

Awhile answered 21/4, 2013 at 18:35 Comment(10)
this gets to my problem. get_user_name is in a different module than test_method. Is there a way to mock something in a sub_module? I fixed it in an ugly way below.Deglutinate
It doesn't matter that get_user_name is in a different module than test_method since you are importing the function into app.mocking they are in the same namespace.Awhile
Where did test_patch come from, what is it exactly?Chemosphere
test_patch is passed in by the patch decorator and is the mocked get_user_name object (i.e. an instance of the MagicMock class). It might be clearer if it was named something like get_user_name_patch.Awhile
How are you referencing test_method? It will result in error, NameError: global name 'test_method' is not definedBarrows
test_method is just a placeholder for the function being tested, in the question it's imported. I have added the import to match the example code in the question.Awhile
Thank you this was very helpful. It had me thrown for a while that you only need to stub the imported callableSchoenfelder
I think this section should be moved on the very top of the docs.Styles
Thank you very much! Despite having read the "Where to patch" section of the docs, I didn't fully grok it until I read your answer.Emergent
One little caveat that I tripped over just now was that the "from mock import patch" line must be the first line. If you import test_method first, and then import patch, it won't work. Just wanted to highlight that.Halitosis
B
26

While Matti John's answer solves your issue (and helped me too, thanks!), I would, however, suggest localizing the replacement of the original 'get_user_name' function with the mocked one. This will allow you to control when the function is replaced and when it isn't. Also, this will allow you to make several replacements in the same test. In order to do so, use the 'with' statment in a pretty simillar manner:

from mock import patch

class MockingTestTestCase(unittest.TestCase):

    def test_mock_stubs(self):
        with patch('app.mocking.get_user_name', return_value = 'Mocked This Silly'):
            ret = test_method()
            self.assertEqual(ret, 'Mocked This Silly')
Biak answered 20/2, 2015 at 12:21 Comment(1)
This is sort of immaterial to the posed question. Whether you use patch as a decorator or context manager is specific to the use case. For instance you can use patch as a decorator to mock a value for all tests in an xunit or pytest class while in other cases its useful to have the fine grained control afforded by the context manager.Deglutinate
N
4

The accepted answer is correct that using patch you have to consider the namespace in which it is imported. But imagine that you'd want to override the actual implementation on a global basis no matter where it is imported you can monkeypatch the implementation:

@pytest.fixture(autouse=True)
def get_user_name_mock(monkeypatch):
    _mock = MagicMock()
    monkeypatch.setattr(app.my_module, app.my_module.get_user_name.__name__, _mock )
    return _mock 

In your tests, just add the name of the fixture as an argument like default fixture behavior.

In my projects I use this to override my global config value resolvement to catch unmocked config.get calls which could cause undefined behavior.

edit: link to docs: https://pytest.org/en/7.3.x/how-to/monkeypatch.html#how-to-monkeypatch-mock-modules-and-environments

Noni answered 7/7, 2023 at 14:46 Comment(0)
M
0

Building up on the answers already given, especially the one by Matti which helped solved my patching issues (I was patching replacing the original function, and not the function call in the module), I prefer to do it without the additional mock library. I am not sure if this 10 years back was shipped as part of the standard Python installation, but today, it is a separate installation and I would much rather keep my pyproject.toml as clean and lightweight as possible. Here's how I solved it with just unnitest.


from app.mocking import test_method
import unittest
from unittest.mock import patch


class MockingTestTestCase(unittest.TestCase):
    @patch("app.mocking.get_user_name")
    def test_mock_stubs(self, test_patch):
        test_patch.return_value = "Mocked This Silly"
        ret = test_method()
        self.assertEqual(ret, "Mocked This Silly")

        self.assertEqual(test_patch.call_count, 1)

I've also added another assertion statement. It's a good idea to make sure calls to specific functions were made. For large code bases with multiple coders, this can be crucial.

Milfordmilhaud answered 2/4, 2024 at 11:2 Comment(0)
O
-3

Besides Matti John's solution, you can also import module instead of function in app/mocking.py.

# from app.my_module import get_user_name
from app import my_module

def test_method():
  return my_module.get_user_name()

if __name__ == "__main__":
  print "Starting Program..."
  test_method()
Oestrogen answered 5/12, 2022 at 13:9 Comment(2)
Why does this get downvoted? Changing the way things get imported has its disadvantages but I can think of many scenarios in which this might actually be better than patching at importing modules.Raising
I think it's not clear how this answers the question. This is just a semantic alternative for addressing the function. At the heart of the question was "Why does this mock not work as expected?" your answer doesn't answer that question.Deglutinate

© 2022 - 2025 — McMap. All rights reserved.