Search code examples
pythonmockingpytestfixtures

Python: setting default side_effect with create_autospec on class instance


I am trying to use side_effect with unittest.mock.create_autospec on class to set default instance call behavior to raise NotImplementedError.

The problem I am facing is:

  • I do not want exception to be raise on class __init__.
  • I do not want to set explicitly all my methods side effect.
  • I want to use a pytest.fixture in order to make my mock reusable through various tests.

Here a code sample of what I am trying to achieve.

# module.py

class MyClass:
    def __init__(self, value):
        self.value = value

    def compute(self):
        return self.value


def foo():
    instance = MyClass(42)
    return instance.compute()
# test_module.py

from unittest.mock import create_autospec
import module
import pytest


@pytest.fixture(autouse=True)
def my_class(monkeypatch):
    # Help me HERE to set side_effect, bellow tests will not work with this settings.
    spec_cls = create_autospec(module.MyClass, side_effect=NotImplementedError)

    monkeypatch.setattr(module, "MyClass", spec_cls)

    return spec_cls("<whatever>")


def test_foo():
    with pytest.raises(NotImplementedError):
        module.foo()


def test_bar(my_class):
    my_class.compute.return_value = 24
    assert module.foo() == 24

Solution

  • Not need to use autospec :

    import unittest.mock as mocking
    
    import pytest
    
    import so71018132_module as module
    
    
    @pytest.fixture(autouse=True)
    def fake_my_class():
        with mocking.patch.object(module.MyClass, "compute") as compute_mock:
            compute_mock.side_effect = NotImplementedError  # default behaviour
            yield compute_mock
    
    
    def test_foo():
        with pytest.raises(NotImplementedError):
            module.foo()
    
    
    def test_bar(fake_my_class):
        fake_my_class.side_effect = [24]
        # or alternatively, clear the side_effect then set a return_value :
        # fake_my_class.side_effect = None
        # fake_my_class.return_value = 24
        assert module.foo() == 24
    

    passes the 2 tests.

    I completely changed the fixture to use unittest.mock.object.patch on the compute method of MyClass so just that is mocked, the rest of the class is used ordinarily. Also, I had to slightly adjust the test_bar code to correctly alter the mock behavior.