简体   繁体   中英

Python: Mock class constructor to throw an Exception, inside another class's constructor

Problem

To put concisely, I have a class A whose constructor catches any exceptions that might occur. The only logic that can throw an exception though, is the construction of another class B (from an external library, in case that matters). I want to test that when this inner constructor (B) throws an exception, the outer constructor (A) catches this exception.

# module_a.py

from external_module_b import B

class A:

    def __init__(self) -> None:
        try:
            self.b = B(
                b_param_1="...",
                b_param_2="..."
            )
            self.ok = True
            # ...
        except Exception as e:
            self.ok = False
            print_report_request(repr(e))

Attempts

First, I tried using @patch() with side_effect like this:

# test_module_a.py

from unittest import mock, TestCase
from module_a import A

class MyTestCase(TestCase):

    @mock.patch("external_module_b.B")
    def test_constructor_throws(self, mock_b: mock.Mock):
        mock_b.side_effect = Exception("test")
        a = A()
        self.assertFalse(a.ok)

This didn't seem to work—a.ok was True. I tried another suggestion to define side_effect in @patch() itself:

    @mock.patch("external_module_b.B", side_effect=Exception("Test"))
    def test_constructor_throws(self, mock_b: mock.Mock):
        a = A()
        self.assertFalse(a.ok)

a.ok was still True.

I wondered if something's wrong with the string I'm giving to @patch(). But typing "external_module_b" in code, PyCharm's autocomplete did suggest "external_module_b.B". (I'm not sure whether that's a valid proof or not.) I tried yet another suggestion that uses raiseError. I also tried making side_effect a function (lambda). But I think it's more likely that I'm misunderstanding something fundamental. Possibly to do with mocking constructors / classes.

Ah. Figured it out. It had nothing to do with constructors or the side_effect syntax or autospec'ing or any of the things I was trying. Reading Where to patch was the key. I'll explain in a way that made sense to me (as a beginner, for beginners):

I thought in @patch() I was supposed to refer to the "full path" of the class I wanted to affect— ie where it came from originally , eg "external_module_b.B".

But this only does its thing when the code you want to test also constructs the class from its original source , eg constructs via external_module_b.B().

But in my case, I had imported B into module_a , and constructed that via B(). So it's actually module_a.B that I needed to patch, not external_module_b.B.

So this worked for me:

# test_module_a.py

from unittest import mock, TestCase
from module_a import A

class MyTestCase(TestCase):

    @mock.patch("module_a.B") # <-- This was the only change I needed.
    def test_constructor_throws(self, mock_b: mock.Mock):
        mock_b.side_effect = Exception("test")
        a = A()
        self.assertFalse(a.ok)

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM