17

I have been working with Python's unittest.mock library quite a bit, but right now I'm struggling with a use case that may not be approached correctly.

Consider a file mymodule/code.py containing the following snippet:

def sum():
  pass

def mul():
  pass

def div():
  pass

def get_functions():
  return [sum, mul, div]

def foo():
  functions = get_functions()
  for func in functions:
    func()

I want to test the foo function, patching the sum function, and leaving mul and div as they are. This is what I tried initially:

class TestFoo(unittest.TestCase):
  @mock.patch('mymodule.code.foo.sum')
  def test_foo(foo_sum_mock):
    foo()
    foo_sum_mock.assert_called_once()

However, the patching approach illustrated above does not work. I believe that the sum function is patched correctly when loading mymodule.code.py, but redefined due to the def sum() block.

By reading the official documentation, I also tried to use the start and stop functions of the unittest.mock library as follows:

def test_foo():
  patcher = mock.patch('module.code.sum')
  mocked_sum_fun = patcher.start()

  foo()

  mocked_sum_fun.assert_called_once()
  mock_sum_fun.stop()

This approach also did not work. I was hoping it would avoid the sum function override after the modules/code.py file gets loaded.

Is it possible to patch a local function such as sum? Or is moving the sum function to another file the only option for patching?

Many thanks in advance!

1
  • 2
    Did you ever figure this out? I'm having the same issue. Commented Jul 17, 2021 at 0:36

2 Answers 2

6

You can mock a function of same module using mock.patch and refering this module as __main__

code.py

from unittest.mock import patch

def sum():
    print("called method sum")
    pass

def call_sum():
    sum()

def return_mock():
    print("I'm a mocked method")
    return True

with patch('__main__.sum', return_value=return_mock()) as mock_test:
    call_sum()
    mock_test.assert_called_once() # assure that mocked method was called, not original.

You could also use the path of lib (my_project.code.sum) instead of __main__.sum.

Sign up to request clarification or add additional context in comments.

5 Comments

Getting ModuleNotFoundError, '__main__' is not a package.
@Velkan check here the code running
@Velkan in this example, all the code is in just one file.
It depends heavily on how it's called. I've found somewhere that the reliable way is to use something like __name__ + '.sum'.
@Velkan You are right and the answer is misleading, you might want to edit it.
-3

Generally speaking, you'd want to separate your test code from your production code:

code.py

def sum():
    pass

def mul():
    pass

def div():
    pass

def get_functions():
    return [sum, mul, div]

def foo():
    functions = get_functions()
    for func in functions:
        func()

code_test.py

import unittest
import mock_test as mock
import code

class TestFoo(unittest.TestCase):
    @mock.patch('code.sum')
    def test_foo(self, sum_mock):
        def new_sum_mock(*args, **kwargs):
            # mock code here
            pass
        sum_mock.side_effect = new_sum_mock
        code.foo()
        sum_mock.assert_called_once()

But yes, you could place it all into one file:

code_test.py:

import unittest
import mock_test as mock
import code

def sum():
    pass


def mul():
    pass


def div():
    pass


def get_functions():
    return [sum, mul, div]


def foo():
    functions = get_functions()
    for func in functions:
        func()


class TestFoo(unittest.TestCase):

    @mock.patch('code_test.sum')
    def test_foo(self, sum_mock):
        def new_sum_mock(*args, **kwargs):
            # mock code here
            pass

        sum_mock.side_effect = new_sum_mock
        code.foo()
        sum_mock.assert_called_once()

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.