Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does @mock.patch know which parameter to use for each mock object?

Looking at this webpage: http://www.toptal.com/python/an-introduction-to-mocking-in-python -- The author talks about Mocking and Patching in Python and gives a pretty solid "real-world" example. The part that is tripping me up is understanding how the unit testing frame work knows which mock object gets passed to which patch.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import os.path

def rm(filename):
    if os.path.isfile(filename):
        os.remove(filename)

Code sample is pretty easy to understand. Hard-coded dependency on the OS library/module. First checks if the file exists using the os.path.isfile() method and if so, removes it using os.remove()

Test/Mock code is as follows:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from mymodule import rm

import mock
import unittest

class RmTestCase(unittest.TestCase):

    @mock.patch('mymodule.os.path')
    @mock.patch('mymodule.os')
    def test_rm(self, mock_os, mock_path):
        # set up the mock
        mock_path.isfile.return_value = False

        rm("any path")

        # test that the remove call was NOT called.
        self.assertFalse(mock_os.remove.called, "Failed to not remove the file if not present.")

        # make the file 'exist'
        mock_path.isfile.return_value = True

        rm("any path")

        mock_os.remove.assert_called_with("any path")

I guess what's confusing me is that there are 2 @Patch calls and 2 parameters passed in the test. How does the unit testing framework know that mymodule.os.path is patching os.path and that it is mapped to mock_path? And where is mymodule.os.path defined?

(There appears to be a lot of "magic" going on and I'm not following it.)

like image 351
Pretzel Avatar asked Oct 26 '15 19:10

Pretzel


People also ask

How does a mock object work?

A object that you want to test may have dependencies on other complex objects. To isolate the behavior of the object you want to test you replace the other objects by mocks that simulate the behavior of the real objects. So in simple words, mocking is creating objects that simulate the behavior of real objects.

What does mock patch object do?

mock provides a powerful mechanism for mocking objects, called patch() , which looks up an object in a given module and replaces that object with a Mock . Usually, you use patch() as a decorator or a context manager to provide a scope in which you will mock the target object.

How does the patch decorator work Python?

Patch can be used as a TestCase class decorator. It works by decorating each test method in the class. This reduces the boilerplate code when your test methods share a common patchings set. patch finds tests by looking for method names that start with patch.

How does Magic mock work?

MagicMock objects provide a simple mocking interface that allows you to set the return value or other behavior of the function or object creation call that you patched. This allows you to fully define the behavior of the call and avoid creating real objects, which can be onerous.


2 Answers

it goes by the order of the execution of the decorators and that is also the order of the parameters passed on to your test method...

order of decorators execution is shown here: https://thadeusb.com/weblog/2010/08/23/python_multiple_decorators/

When you use patch the way you wrote it, a Mock instance it is automatically created for you and passed as a parameter to your test method. there is another version of it:

@mock.patch("subprocess.check_output", mock.MagicMock(return_value='True'))
def test_mockCheckOutput(self):
    self.assertTrue(subprocess.check_output(args=[])=='True')

in this case you pass your own Mock object and in this example, when you call subprocess.check_output(), it will return 'True'

you could however do:

def test_mockCheckOutput(self):
    m = mock.MagicMock(return_value='True')
    with mock.patch("subprocess.check_output", m):
        self.assertTrue(subprocess.check_output(args=[])=='True')

and in this case you can pass any mock item you want because it will be evaluated during runtime... :)

like image 182
eyalsn Avatar answered Oct 16 '22 17:10

eyalsn


When applying a decorator, it is good to look at it like this

<wrapper1>
    <wrapper2>
        <wrapper3>
           **Your Function**
        </wrapper3>
    </wrapper2>
</wrapper1>

Basically your function is required to interact with the wrappers in this order:

wrapper3-> wrapper2->wrapper1

@wrapper1
@wrapper2
@wrapper3
def your_func(wrapper1.input, wrapper2.input, wrapper3.input):

NOTE wrapper1.input isn't how you would actually reference its input

To answer the second part of your question, how mymodule.os knows to refer to os. When Patching you are effectively intercepting calls to that specific name. When you call os in mymodule you are effectively calling mymodule.os. When patching you must refer to the class that is being mocked by the way it is being called in the actual code, not from the test modules perspective

like image 35
DanHabib Avatar answered Oct 16 '22 18:10

DanHabib