Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mocking print but allow using it in tests

Print can be mocked in the following way:

import unittest
import builtin

class TestSomething(unittest.TestCase):
    @mock.patch('builtins.print')
    def test_method(self, print_):
        some_other_module.print_something()

However this means that in the python debug console (pydev debugger) and in the unit test method itself print cannot be used. This is rather inconvenient.

Is there a way to only mock the print method in some_other_module instead of in the testing module as well?

A way to sidestep this is to swap the use of print in the test module with some other function which just calls print, which I can do if there turns out to be no better solution.

like image 362
simonzack Avatar asked Dec 12 '22 02:12

simonzack


2 Answers

@michele's "final solution" has an even cleaner alternative which works in my case:

from unittest import TestCase
from unittest.mock import patch

import module_under_test


class MyTestCase(TestCase):
    @patch('module_under_test.print', create=True)
    def test_something(self, print_):
        module_under_test.print_something()
        print_.assert_called_with("print something")
like image 126
simonzack Avatar answered Feb 28 '23 02:02

simonzack


Yes you can! ... But just because you are using Python 3. In Python 3 print is a function and you can rewrite it without change the name. To understand the final solution I'll describe it step by step to have a final flexible and non intrusive solution.

Instrument Module

The trick is add at the top of your module that you will test a line like:

print = print

And now you can patch just print of your module. I wrote a test case where the mock_print_module.py is:

print = print

def print_something():
    print("print something")

And the test module (I'm using autospec=True just to avoid errors like mock_print.asser_called_with):

from unittest import TestCase
from unittest.mock import patch
import mock_print_module

class MyTestCase(TestCase):
    @patch("mock_print_module.print",autospec=True)
    def test_something(self,mock_print):
        mock_print_module.print_something()
        mock_print.assert_called_with("print something")

I don't want to change my module but just patch print without lose functionalities

You can use patch on "builtins.print" without lose print functionality just by use side_effect patch's attribute:

@patch("builtins.print",autospec=True,side_effect=print)
def test_somethingelse(self,mock_print):
    mock_print_module.print_something()
    mock_print.assert_called_with("print something")

Now you can trace your prints call without lose the logging and pydev debugger. The drawback of that approach is that you must fight against lot of noise to check your interested the print calls. Moreover you cannot chose what modules will be patched and what not.

Both modes don't work together

You cannot use both way together because if you use print=print in your module you save builtins.print in print variable at the load module time. Now when you patch builtins.print the module still use the original saved one.

If you would have a chance to use both you must wrap original print and not just record it. A way to implement it is use following instead of print=print:

import builtins
print = lambda *args,**kwargs:builtins.print(*args,**kwargs)

The Final Solution

Do we really need to modify the original module to have a chance to patch all print calls in it? No, we can do it without change the module to test anyway. The only thing that we need is injecting a local print function in the module to override the builtins's one: we can do it in the test module instead of the the module to test. My example will become:

from unittest import TestCase
from unittest.mock import patch
import mock_print_module

import builtins
mock_print_module.print = lambda *args,**kwargs:builtins.print(*args,**kwargs)

class MyTestCase(TestCase):
    @patch("mock_print_module.print",autospec=True)
    def test_something(self,mock_print):
        mock_print_module.print_something()
        mock_print.assert_called_with("print something")

    @patch("builtins.print",autospec=True,side_effect=print)
    def test_somethingelse(self,mock_print):
        mock_print_module.print_something()
        mock_print.assert_called_with("print something")

and mock_print_module.py can be the clean original version with just:

def print_something():
    print("print something")
like image 24
Michele d'Amico Avatar answered Feb 28 '23 03:02

Michele d'Amico