Repository: Azure/azure-functions-durable-python

Issue:
EntityId Class Equality Check Improvement for Better Unit Test Experience
💡 **Feature description**


The `EntityId` class does check not check equality based on `key` and `name` but on class instance. This causes poor user experience for unit tests as asserting calls on the unittest `Mock` class will fail.

Imagine the following orchestrator code:

```python
@blueprint.orchestration_trigger(context_name="context")
def orchestration(context: DurableOrchestrationContext) -> Generator[dict[str, Any], Any, Any]:
    return _orchestration(context)

def _orchestration(context: DurableOrchestrationContext) -> Generator[dict[str, Any], Any, Any]:

    # do something

    yield context.call_entity(
        EntityId('example', '123'),
        "set",
        {"key": "value"},
    )

    # do something
```

And the following unit test:

```python

def test_signal_entity(mocker: pytest_mock.MockFixture) -> None:
    # Given
    call_entity_mock = Mock()

    context_mock = Mock()
    context_mock.call_entity = call_entity_mock

    orchestration = _orchestration(context_mock)

    # When
    next(orchestration)

    # Then
    call_entity_mock.assert_called_with(
        EntityId('example', '123'),
        "set",
        {"key": "value"},
    )
```

Changing the `EntityId` class to check equality based on `key` and `name` will make the unit test pass and provide a better user experience.


💭 **Describe alternatives you've considered**
We considered just using asserting the call (i.e., called x times) and then individually asserting the call args by explicitly checking the key. However, this still prevents using the `assert_called_with` api.

**Additional context**
Addressed by #520

Discussion:
@nytian: Close as completed by https://github.com/Azure/azure-functions-durable-python/pull/520

Solution patch:
diff --git a/azure/durable_functions/models/utils/entity_utils.py b/azure/durable_functions/models/utils/entity_utils.py
index f5669323..aebdb113 100644
--- a/azure/durable_functions/models/utils/entity_utils.py
+++ b/azure/durable_functions/models/utils/entity_utils.py
@@ -89,3 +89,15 @@ def __str__(self) -> str:
             A SchedulerId-based string representation of the EntityId
         """
         return EntityId.get_scheduler_id(entity_id=self)
+
+    def __eq__(self, other: object) -> bool:
+        """Check if two EntityId objects are equal.
+
+        Parameters
+        ----------
+        other: object
+        """
+        if not isinstance(other, EntityId):
+            return False
+
+        return self.name == other.name and self.key == other.key


Test patch:
diff --git a/tests/utils/__init__.py b/tests/utils/__init__.py
new file mode 100644
index 00000000..8b137891
--- /dev/null
+++ b/tests/utils/__init__.py
@@ -0,0 +1 @@
+
diff --git a/tests/utils/test_entity_utils.py b/tests/utils/test_entity_utils.py
new file mode 100644
index 00000000..b47a6512
--- /dev/null
+++ b/tests/utils/test_entity_utils.py
@@ -0,0 +1,24 @@
+import pytest
+from azure.durable_functions.models.utils.entity_utils import EntityId
+
+@pytest.mark.parametrize(
+    ("name_e1", "key_1", "name_e2", "key_2", "expected"),
+    [
+        ("name1", "key1", "name1", "key1", True),
+        ("name1", "key1", "name1", "key2", False),
+        ("name1", "key1", "name2", "key1", False),
+        ("name1", "key1", "name2", "key2", False),
+    ],
+)
+def test_equal_entity_by_name_and_key(name_e1, key_1, name_e2, key_2, expected):
+
+    entity1 = EntityId(name_e1, key_1)
+    entity2 = EntityId(name_e2, key_2)
+
+    assert (entity1 == entity2) == expected
+
+def test_equality_with_non_entity_id():
+
+    entity = EntityId("name", "key")
+
+    assert (entity == "not an entity id") == False