SynackSA
SynackSA

Reputation: 909

Pytest - Mocking property and setter

I'm having a real hard time understanding how to mock a class that has a property and setter fixtures that access a "private" attribute.

import pytest
from pytest_mock import MockFixture
from unittest.mock import MagicMock
from typing import List

class MyEntity:

    _my_list: List[str] = []

    def __init__(self, list_vals = []):
        self._my_list = list_vals

    @property
    def my_list(self) -> List[str]:
        return self._my_list

    @my_list.setter
    def my_list(self, value: List[str]):
        self._my_list = value

    def append(self, value: str):
        self._my_list.append(value)


class Foo:

    def generate_entity(self, list_vals: List[str]) -> MyEntity:
        return MyEntity()

    def set_values(self, entity: MyEntity, list_vals: List[str]) -> MyEntity:
        entity.my_list = list_vals
        return entity

    def add_value(self, entity: MyEntity, value: str) -> MyEntity:
        entity.append(value)
        return entity


@pytest.fixture
def mock_my_entity(mocker: MockFixture) -> MagicMock:

    namespace = f"{__name__}.{MyEntity.__name__}"
    mock_my_entity = mocker.patch(namespace, autospec=True)

    return mock_my_entity.return_value

def test_foo(mock_my_entity):

    expect_list_values = ["Hello", "World"]

    foo = Foo()
    entity = foo.generate_entity(expect_list_values)
    assert len(entity._my_list) == 0

    entity = foo.set_values(entity, expect_list_values)
    assert entity._my_list == expect_list_values

    expected_extra_value = "more"
    entity = foo.add_value(entity, expected_extra_value)
    assert entity._my_list == expect_list_values + expected_extra_value

After running the test, this is what I get:

mock_my_entity = <NonCallableMagicMock name='MyEntity()' spec='MyEntity' id='4351847632'>

    def test_foo(mock_my_entity):
    
        expect_list_values = ["Hello", "World"]
    
        foo = Foo()
        entity = foo.generate_entity(expect_list_values)
        assert len(entity._my_list) == 0
    
        entity = foo.set_values(entity, expect_list_values)
>       assert entity._my_list == expect_list_values
E       AssertionError: assert <MagicMock na...='4362862544'> == ['Hello', 'World']
E         Right contains 2 more items, first extra item: 'Hello'
E         Full diff:
E         - <MagicMock name='MyEntity()._my_list' spec='list' id='4362862544'>
E         + ['Hello', 'World']

scratchpad.py:58: AssertionError

I've also tried using PropertyMock in various ways, but nothing seems to work.

How do I get this to work. How do I mock the MyEntity class so that everything works the way it should?

Upvotes: 1

Views: 1914

Answers (1)

MrBean Bremen
MrBean Bremen

Reputation: 16855

I'm still not sure I understand what you want to do, but I'll propose a possibility that could fix the test as shown here, comments inline.

def test_foo(mock_my_entity):
    def append(value):
        mock_my_entity.my_list.append(value)

    expect_list_values = ["Hello", "World"]

    foo = Foo()
    # for `append` in the mock to behave as in the original class,
    # we have to change it
    mock_my_entity.append = append
    entity = foo.generate_entity(expect_list_values)
    assert len(entity.my_list) == 0

    # in this case, the list is directly passed to your class,
    # so I prevent that it will change with changes in the class by copying it
    # in reality, this will probably be done in MyEntity
    entity = foo.set_values(entity, expect_list_values.copy())

    # I changed this and further checks to check for the property 
    # instead of the attribute, as this has the same semantics
    assert entity.my_list == expect_list_values

    expected_extra_value = "more"
    entity = foo.add_value(entity, expected_extra_value)
    assert entity.my_list == expect_list_values + [expected_extra_value]

This will work, but I think this is not what you really need. Just wouldn't fit into the comments...

Upvotes: 1

Related Questions