Unittest in python - Patch
Kexin Tang

After introduced mock in previous section, let’s talk about another powerful module – patch.

Intro

patch is a decorator / context manager, and it can help you use some new stuff (default is Mock()) to replace target.

directly use

1
2
3
# demo.py
def func():
return 1
1
2
3
4
5
6
7
8
9
10
import demo
from unittest.mock import patch

def main():
mock_func = patch("demo.func")
mock_func.return_value = 10
mock_func.start()
demo.func() # 10
mock_func.end()
demo.func() # 1

context manager

1
2
3
# demo.py
def func():
return 1
1
2
3
4
5
6
7
8
import demo
from unittest.mock import patch

def main():
with patch("demo.func") as mock_func:
mock_func.return_value = 10
demo.func() # 10
demo.func() # 1

decorator

1
2
3
# demo.py
def func():
return 1
1
2
3
4
5
6
7
import demo
from unittest.mock import patch

@patch("demo.func")
def test_main(mock_func):
mock_func.return_value = 10
demo.func() # 10

notice 0

Please notice, if we have multiple @patch for one function, the order is important: the inner decorator decorates front parameter.

1
2
3
4
@patch("demo.func2")
@patch("demo.func1")
def test(mock_func1, mock_func2):
...

notice 1

Decorator can also be used in class. Remember patch is used for test, so

  • only function name starts with test_ will be treat as test function
  • only class derives from unittest.TestCase will be treat as test class
1
2
3
4
5
6
7
8
9
10
@patch("demo.func")
class MyTest(unittest.TestCase):
def test_1(self, mock_func):
...
def test_2(self, mock_func):
...

# patch is not working
def mytest_func(self):
...

patch

unittest.mock.patch(target, new=DEFAULT, spec=None, create=False, spec_set=None, autospec=None, new_callable=None, **kwargs)

  • target: target object’s path, remember it must be a string looks like package.module.className. If the object is defined in the same file, please use __main__.className.
  • new: default is MagicMock(), it can be a value or a actual object.
  • new_callable: it is a callable to create object.
  • spec & spec_set: please refer Mock parts
1
2
3
4
5
6
7
8
def new_func():
return 10

def main():
mock_func = patch("demo.func", new = new_func)
mock_func.start()
demo.func() # new_func() -> 10
mock_func.end()

patch target path

If we want to patch some function, the path is not where we define the function, is where we use it.

1
2
3
4
# package2.m2.py
from package1.m1 import func1
def func2():
func1()
1
2
# test.py
@patch("package2.m2.func1")

new vs return_value

1
2
3
4
5
6
7
8
9
10
11
def main():
mock_func = patch("demo.func", return_value = 10)
mock_func.start()
demo.func() # 10
mock_func.end()

def main():
mock_func = patch("demo.func", new = 10)
mock_func.start()
demo.func # 10
mock_func.end()

new vs new_callable

new is an instance, new_callable is a callable to create instance.

1
2
3
4
5
foo = 10
with patch("__main__.foo", new = 100):
foo
with patch("__main__.foo", new_callable = lambda: 100):
foo
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
def return_100():
return 100

def main():
mock_func = patch("demo.func", new = return_100)
mock_func.start()
demo.func() # 100
mock_func.end()

# don't recommend to assign a value to new
def main():
mock_func = patch("demo.func", new_callable = return_100)
mock_func.start()
demo.func # 100
mock_func.end()

Cannot use ‘new’ and ‘new_callable’ together!

1
patch("demo.func", new = xxx, new_callable = xxx)   # error

how to patch a whole class

1
2
3
4
5
6
7
8
9
# util.py
class Object:
def __init__(self, x, y):
self.x = x
self.y = y

def show(self):
print(f"x = {x}, y = {y}")
return 0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# keep this the same as __init__
def constructor(self, x, y):
self = Mock(spec = Object)
self.x = 2 * x
self.y = 2 * y
self.show.return_value = 100
return self

@patch("util.Object", new = constructor)
def test_patch_with_new():
o = Object(10, 10)
res = o.show() # x = 20, y = 20
print(res) # 100
o.x = 1000
print(o.x) # 1000

patch.object

Used to mock methods in one class.

1
2
3
4
5
6
7
8
import Object

# only mock func inside Object class
@patch.object(Object, "func")
def test_patch_object(mock_func):
mock_func.return_value = 100
o = Object()
o.func() # 100

patch.dict

1
2
3
4
m = {"a": 1, "b": 2}

with patch.dict(m, {"a": 10, "b": 20}, clear=True):
m["a"] # 10