0

I have a script.py file:

# in script.py
def my_basic_function(value, c):
    return c(value).words()
class BasicClass:
    def __init__(self, val):
        self.val = val
    def words():
        return self.val

and a test.py file:

# in test.py
from mock import patch
from script import my_basic_function, BasicClass

def third_function(self,):
    return "not {}".format(self.val)

def fourth_function():
    return "not ponies"

def init_mock(self, val):
    self.val = val

@patch('script.BasicClass.words', third_function)
@patch('script.BasicClass.__init__', init_mock)
def test_my_basic_function():
    assert my_basic_function("ponies", BasicClass) == "not ponies"

that I can run using pytest test.py from the command line successfully.

If I want to use side_effect in my @patch, I have to do things a little differently:

@patch('script.BasicClass.words', side_effect = fourth_function)
@patch('script.BasicClass.__init__', init_mock)
def test_my_basic_function(amock):
    assert my_basic_function("ponies", BasicClass) == "not ponies"

i.e., I have to:

  • add an argument to test_my_basic_function that I never use.
  • call fourth_function rather than third_function, as I cannot use any class instance variables.

What is the difference between patching in these two ways?

jeremysprofile
  • 10,028
  • 4
  • 33
  • 53
  • 1
    In the first case `BasicClass.words` becomes a `function`, in the second case it is an instance of `Mock` Is that what you wanted to know? You know that a `Mock` and a `function` are different things don't you? – Stop harming Monica Sep 13 '19 at 14:17

1 Answers1

0

You don't need patches for what you are doing (Example A). You are passing in the parameter c which you know to be the class of BasicClass so no need for a patch. You would need a patch if you introduced a function call or object initialization in the function you are testing where you don't want to actually take place.

When using @patch we should use the kwarg side_effect to simulate the raising of exceptions, not calling other functions. If we want to simulate the return value of a function then use kwarg return_value. If we just want to mock a function, then we just use @patch without any kwargs. When we use patches as a decorator we need to pass them into the function. It is true they can be unused but we should use them with mock functions such as assert_called_once or assert_called_once_with to ensure your patches are working as expected. Please see Example B

==============Example A==================

import unittest


def my_basic_function(value, c):
    return c(value).words()


class BasicClass:
    def __init__(self, val):
        self.val = val

    def words(self):
        return self.val


class TestMyBasicFunction(unittest.TestCase):

    def test_my_basic_class(self):
        value = my_basic_function("jeremy", BasicClass)
        self.assertEqual("jeremy", value)

    def test_my_basic_class_wrong(self):
        value = my_basic_function("jeremy", BasicClass)
        self.assertNotEqual("w33b", value)

============Example B======================

import unittest
from unittest.mock import patch


def ensure_str(value):
    try:
        return str(value)
    except Exception:
        raise TypeError


def my_basic_function(value, c):
    value = ensure_str(value)
    return c(value).words()


class BasicClass:
    def __init__(self, val):
        self.val = val

    def words(self):
        return self.val


class TestMyBasicFunction(unittest.TestCase):

    @patch('script.ensure_str', return_value="jeremy")
    def test_return_value(self, ensure_str_mock):
        value = my_basic_function("jeremy", BasicClass)
        ensure_str_mock.assert_called_once_with("jeremy")
        self.assertEqual("jeremy", value)

    @patch('script.ensure_str')
    def test_no_return_value(self, ensure_str_mock):
        value = my_basic_function("jeremy", BasicClass)
        self.assertEqual(ensure_str_mock(), value)

    @patch('script.ensure_str', side_effect=TypeError)
    def test_side_effect(self, ensure_str_mock):
        with self.assertRaises(TypeError):
            value = my_basic_function({'apple': 'sauce'}, BasicClass)
        ensure_str_mock.assert_called_once_with({'apple': 'sauce'})
l33tHax0r
  • 1,384
  • 1
  • 15
  • 31
  • I know that I do not need to `patch` in the first example; it was an attempt to create a [minimum example](https://stackoverflow.com/help/mcve) to ask a question with. – jeremysprofile Dec 23 '19 at 17:26