Search code examples
pythonpython-unittestpython-unittest.mock

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.


Solution

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