0

When unit testing Python code with the unittest standard library, what is the proper way to Mock or MagicMock logging from the code that is being tested? Would the approach outlined in the stripped down, simplified example below be considered Pythonically correct?

Tested code:


class FooOperator:
    def __init__(self, ..., logging=logging):
        self.logging = logging
        ...

    def __get_logging(self):
        return self.logging

    ...

    def some_function(self, arg1, arg2):
        self.logging.info("First info message")
        result = self.some_other_function(arg1=arg1, arg2=arg2)
        if result > 0:
            # Processing records
            ...
        else:
            self.logging.warning(
                 f"Warning message: No records found for {arg1}, {arg2}"
                                )
        ...
        self.logging.info("Second logging message")
        return True

Unit test:

import unittest
from unittest.mock import MagicMock
import logging

from libraries.foo_operator import FooOperator

class Test_some_function(unittest.TestCase):
    def setUp(self):
        self.logging = logging
        self.dag_info = dict(
                             name="valid_dag_name",
                             run_id="valid_dag_run_id",
                             task="valid_dag_task",
                             description="valid_dag_description"
                            )
        ...

    def test_success_logging_warning(self):
        sf_op = FooOperator(None, self.dag_info, logging)
        sf_op._FooOperator__set_logging(self.logging)
        ...
        expected_result = "something"
        self.logging.warning = MagicMock()
        ...
        result = some_function(arg1="valid_arg1", arg2="valid_arg2")
        self.assertEqual(result, expected_result)
        # Assert for logging WARNING
        self.logging.warning.assert_called_once_with(
             f"Warning message: No records found for {arg1}, {arg2}"
                                                    )

    def test_success_logging_info(self):
        sf_op = FooOperator(None, self.dag_info, logging)
        sf_op._FooOperator__set_logging(self.logging)
        ...
        expected_result = "something"
        self.logging.info = MagicMock()
        ...
        result = some_function(arg1="valid_arg1", arg2="valid_arg2")
        self.assertEqual(result, expected_result)
        # Assert for logging INFO
        self.logging.info.assert_has_calls([
                                            call("First info message"),
                                            call("Second info message")
                                           ])
Myklebost
  • 59
  • 8
  • You could create the mock in the test setup and then pass it in via the constructor then you wouldn't need the get and set logging methods at all. This would also make your tests clearer. More generally, I would consider whether there is real value in adding tests to check that the log functions have been called. – Matt Clarke Sep 02 '22 at 07:37
  • @MattClarke I see. I'd say testing logging.warning is important but, in the end, it comes down to coding standards and customs. – Myklebost Sep 02 '22 at 07:54
  • Good point and very true ;) – Matt Clarke Sep 02 '22 at 08:02

0 Answers0