2

I have a few test cases like below examples in which I mock a function that is calling functions not available locally when unit tests are run.

The first statement in the function is_production() is not available so I just would like to mock it to have the function return True (or False in other cases in another Class if I get this working):

def is_production() -> bool:
    db = get_current("XXX")
    ...

class Production(TestCase):
    @patch('runner.runner.is_production')
    def test_modifications(self, get_content_mock):
        get_content_mock.return_value = True
        with tempfile.TemporaryDirectory() as bare_repo_folder:
            with git.Repo.init(bare_repo_folder, bare=False) as repo:
                ...
                # Execute test
                r = Runner(repo.working_dir, dummy.name)
                self.assertRaises(git.exc.RepositoryDirtyError, r.run)

    @patch('runner.runner.is_production')
    def test_no_tag(self, get_content_mock):
        get_content_mock.return_value = True
        with tempfile.TemporaryDirectory() as bare_repo_folder:
            with git.Repo.init(bare_repo_folder, bare=False) as repo:
                with tempfile.TemporaryFile(dir=bare_repo_folder, suffix=".py") as dummy:
                    r = Runner(repo.working_dir, dummy.name)
                    self.assertRaises(RuntimeError, r.run)

This work OK as test run successfully, so patching seems to work as I desire.

Since I need to add a bunch more I was hoping I could just patch a new class derived from TestCase and put all test cases in this derived class. This way I would only need to define the patch once as suggested in this suggestion on SO but I fail to get it working. This is what I currently have:

from unittest.mock import patch
from unittest import TestCase

...

class ProductionTestCase(TestCase):
    def setUp(self):
        self.patcher = patch("runner.runner.is_production", True)
        self.is_production = self.patcher.start()
        self.addClassCleanup(self.patcher.stop())


class Production(ProductionTestCase):
    def test_modifications(self):
        with tempfile.TemporaryDirectory() as bare_repo_folder:
        ...

    def test_no_tag(self):
        with tempfile.TemporaryDirectory() as bare_repo_folder:
        ...

Apparently I have not gotten the patch in place correctly as the actual function seems to get called when unit tests are run:

Error
Traceback (most recent call last):
  File "XXX\runner\tests\test_clinical.py", line 48, in test_modifications
    self.assertRaises(git.exc.RepositoryDirtyError, r.run)
  File "XXX\runner\runner.py", line 67, in run
    self.check_repo()
  File "XXX\runner\runner.py", line 52, in check_repo
    if is_production():
  File "XXX\runner\utils.py", line 33, in is_production
    db = get_current("XXX")
NameError: name 'get_current' is not defined

Any pointers in how to properly patch is_production in module runner.runner and have it return True when running the unit tests?

Jonathan
  • 748
  • 3
  • 20
  • I see that the start and stop are defined setup. Maybe, the patch stopper should be in the teardown. – Roxy Oct 08 '21 at 10:27
  • I think that is a common misconception. Even the [docs on `start()` and `stop()`](https://docs.python.org/3/library/unittest.mock.html#patch-methods-start-and-stop) state: "Caution If you use this technique you must ensure that the patching is “undone” by calling stop. This can be fiddlier than you might think, because if an exception is raised in the setUp then tearDown is not called. unittest.TestCase.addCleanup() makes this easier:" – Jonathan Oct 08 '21 at 13:05

1 Answers1

1

This worked for me:

@patch('runner.runner.is_production', True)
class Production(TestCase):
    
    def test_modifications(self, get_content_mock):
        ...

    def test_no_tag(self, get_content_mock):
        ...
jimmybutton
  • 143
  • 1
  • 9