0

I would like to assert from a UT, TestRunner.test_run that some deeply nested function Prompt.run_cmd is called with the string argument "unique cmd". My setup besically resembles the following:

# Module application/engine/prompt.py
class Prompt:
    def run_cmd(self, input):
        pass
        
# Module: application/scheduler/runner.py   
class Runner:
    def __init__(self):
        self.prompt = application.engine.prompt.Prompt()

    def run(self):
        self.prompt.run_cmd("unique cmd")
        
        
# Module tests/application/scheduler/runner_test.py
class TestRunner(unittest.TestCase):
    ...
    def test_run(self):
        # calls Runner.run
        # Objective assert that Prompt.run is called with the argument "unique cmd"
        # Current attempt below:
        with mock.patch(application.engine.prompt, "run_cmd") as mock_run_cmd:
           pass

Unfortunately my attempts to mock the Prompt.run_cmd fail with the error message

AttributeError: 'module' object has no attribute 'object'

Olumide
  • 5,397
  • 10
  • 55
  • 104
  • `mock.patch()` takes a string argument, not an object. Maybe you mean `mock.patch.object()`? – MrBean Bremen Jun 21 '22 at 17:55
  • @MrBeanBremen Thanks. `mock.patch()` is what I meant to use. What I'm trying to do is to assert that the argument "unique_cmd" is passed to `Prompt.run_cmd()`. The slight problem is that `Prompt.run_cmd()` is called often by other functions and so it cannot be mocked away. I'd like to swap `Prompt.run_cmd()` with a function `Bar.foo()` that (1) forwards all invocations to `Prompt.run_cmd()` and (2) can assert whether "unique_cmd" was passed to `Prompt.run_cmd()`. – Olumide Jun 22 '22 at 15:46

1 Answers1

1

If you wanted to patch a concrete instance, you could easily do this using mock.patch.object and wraps (see for example this question.

If you want to patch your function for all instances instead, you indeed have to use mock.patch. In this case you could only mock the class itself, as mocking the method would not work (because it is used on instances, not classes), so you cannot use wraps here (at least I don't know a way to do this).

What you could do instead is derive your own class from Prompt and overwrite the method to collect the calls yourself. You could then patch Prompt by your own implementation. Here is a simple example:

class MyPrompt(Prompt):
    calls = []

    def run_cmd(self, input):
        super().run_cmd(input)
        # we just add a string in the call list - this could be more sophisticated
        self.__class__.calls.append(input)

class TestRunner(unittest.TestCase):

    def test_run(self):
        with mock.patch("application.engine.prompt.Prompt", MyPrompt) as mock_prompt:
            runner = Runner()
            runner.run()
            self.assertEqual(["unique cmd"], mock_prompt.calls)
MrBean Bremen
  • 14,916
  • 3
  • 26
  • 46