NickJaremek
NickJaremek

Reputation: 265

Python: patching function defined in same module of tested function

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!

Upvotes: 17

Views: 5002

Answers (2)

Mauro Baraldi
Mauro Baraldi

Reputation: 6575

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.

Upvotes: 6

Javon
Javon

Reputation: 55

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()

Upvotes: -3

Related Questions