简体   繁体   中英

Python unittest.mock: patched class method called but assertion fails

I'm trying to mock several components of a utility class. While assert_called() is ok for one method it fails for an other but I am sure that both are called. I'm running Python 3.7.3 on Windows 10.

I have stripped down my scenario to the essentials. The utility class ( util.py ):

class api:

    @staticmethod    
    def send(data):
        print("sending %s" % data)

    class logger:

        @staticmethod
        def info(s):
            print("INFO: %s" % s)

        @staticmethod
        def error(s):
            print("ERROR: %s" % s)

The unit test variant that works fine:

import unittest
from unittest.mock import patch
from util import api

def do_something():
    api.logger.info("doing something")
    api.send("some data")

@patch("util.api.logger")
class Test(unittest.TestCase):

    def test_do_something(self, mock_logger):
        do_something()
        mock_logger.info.assert_called()

The one that fails:

import unittest
from unittest.mock import patch
from util import api

def do_something():
    api.logger.info("doing something")
    api.send("some data")

@patch("util.api")
class Test(unittest.TestCase):

    def test_do_something(self, mock_api):
        do_something()
        mock_api.send.assert_called()

When running the tests I am getting both print() outputs:

INFO: doing something
sending some data

so I'm sure that both methods are called.

Very likely I'm making some stupid dummy mistake because I'm really new to python...

Some more background:

In my stripped down scenario do_something() is just a stand-in for a set of functions that are the object of my test and that in my real scenario are actually defined in a separate python file. In the production environment it runs in the context of a framework that provides the utility class api. In my test environment util.py itself is a mock of the production api. The py file to be tested (instead of def do_something(): ... ) is loaded like so:

path = os.getcwd() + "<local path to py file to be tested>"
globals().update({ **runpy.run_path(path, init_globals=globals()), **globals() })

Therefore, I can't modify the do_something() code in the test scenario.

This was a tricky one.

The issue is that the api class in imported by the test class well before it is patched. From the docs for patch :

The target is imported when the decorated function is executed, not at decoration time.

Since you import the api class first thing in the test class, that guy is not getting patched when you run the test. Python thinks it's already imported.

Note this is not the case with the logger class since you never import it. If you had, the first test would've failed as well.

Simplest fix - move the import inside the test function.

def do_something():
     from util import api
     api.logger.info("doing something")
     api.send("some data")

Now patch will do the importing - so the mock is placed in the test scope instead of the actual class.

Finally got it after having found this SO question - I wasn't aware that you can also patch individual functions/class methods (not mentioned in the unittest.mock.patch reference ). So this works:

import unittest
from unittest.mock import patch
from util import api

def do_something():
    api.logger.info("doing something")
    api.send("some data")

@patch("util.api.send")
@patch("util.api.logger")
class Test(unittest.TestCase):

    def test_do_something(self, mock_logger, mock_send):
        do_something()
        mock_send.assert_called()
        mock_logger.info.assert_called()

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM