Repository: kaste/mockito-python

Issue:
multiple when not working when spec added to mock
```
from mockito import *
test = mock({"__eq__": lambda x: False})
when(test).f(3).thenReturn(1)
when(test).f(4).thenReturn(2)

test.f(3)
```

expected: 1
actual:
```
mockito.invocation.InvocationError: 
Called but not expected:

    f(3)

Stubbed invocations are:

    f(4)

```

It works all fine if you leave out the dict with the "eq" in mock()


The same issue also occurs if you use eg any(float)  and any(int) instead of hard numbers 3 and 4

Hints from issue comments:
@kaste: That's just when you fake `__eq__` because then multiple calls to `when(test)` never find the mock in the registry as `==` always returns `False`.  (I guess I could use `is` as well but there must be another dunder method to implement/overwrite that so this wouldn't be a solution to the general problem.)  

It's better to use `when2` here for patching `__eq__` as it gives you more options, i.e. it can restrict the `False` answer to a specific given argument and call the original implementation for all other cases.  

@kaste: There is no `is`-dunder method in Python and using `==` seems like a mistake here.  The registry actually is a mapping from user-visible-objects to internal-state.  Similar to proxy objects, the internal objects belong to the user objects in a 1-to-1 fashion.  They are extensions.  Typically this data would be represented by a dict (as it is a mapping) but we cannot require user-objects to be hashable so we search-and-find by iterating over all values in the registry using `==`. This seems to be a mistake because data-objects could be equal to each other.  We want identity checks.  Let's see if such a change breaks a unit test. @Wouter1: @kaste thanks for addressing this issue!
Is this already available, which version do I need to pip install?@kaste: I published right after.  Should be in v1.5.2.

Solution patch:
diff --git a/mockito/mock_registry.py b/mockito/mock_registry.py
index f1e69da..2b6ebf7 100644
--- a/mockito/mock_registry.py
+++ b/mockito/mock_registry.py
@@ -28,7 +28,7 @@ class MockRegistry:
     """
 
     def __init__(self):
-        self.mocks = _Dict()
+        self.mocks = IdentityMap()
 
     def register(self, obj, mock):
         self.mocks[obj] = mock
@@ -54,8 +54,7 @@ def get_registered_mocks(self):
 
 
 # We have this dict like because we want non-hashable items in our registry.
-# This is just enough to match the invoking code above. TBC
-class _Dict(object):
+class IdentityMap(object):
     def __init__(self):
         self._store = []
 
@@ -64,7 +63,7 @@ def __setitem__(self, key, value):
         self._store.append((key, value))
 
     def remove(self, key):
-        self._store = [(k, v) for k, v in self._store if k != key]
+        self._store = [(k, v) for k, v in self._store if k is not key]
 
     def pop(self, key):
         rv = self.get(key)
@@ -76,7 +75,7 @@ def pop(self, key):
 
     def get(self, key, default=None):
         for k, value in self._store:
-            if k == key:
+            if k is key:
                 return value
         return default
 


Test patch:
diff --git a/tests/issue_86_test.py b/tests/issue_86_test.py
new file mode 100644
index 0000000..9a005a1
--- /dev/null
+++ b/tests/issue_86_test.py
@@ -0,0 +1,27 @@
+import pytest
+from mockito import when
+
+
+class Data:
+    def __init__(self, name):
+        self.name = name
+
+    def __eq__(self, other):
+        if isinstance(other, Data):
+            return self.name == other.name
+        return NotImplemented
+
+    def tell_name(self):
+        return self.name
+
+
+@pytest.mark.usefixtures('unstub')
+class TestIssue86:
+    def testValueObjectsAreTreatedByIdentity(self):
+        a = Data("Bob")
+        b = Data("Bob")
+        when(a).tell_name().thenReturn("Sarah")
+        when(b).tell_name().thenReturn("Mulder")
+        assert a.tell_name() == "Sarah"
+        assert b.tell_name() == "Mulder"
+
diff --git a/tests/my_dict_test.py b/tests/my_dict_test.py
index bfab349..5c38b3f 100644
--- a/tests/my_dict_test.py
+++ b/tests/my_dict_test.py
@@ -1,62 +1,54 @@
 
-import pytest
+from mockito.mock_registry import IdentityMap
 
-from mockito.mock_registry import _Dict
-from mockito.mocking import Mock
 
+class TestIdentityMap:
 
-class TestCustomDictLike:
-
-    def testAssignKeyValuePair(self):
-        td = _Dict()
-        obj = {}
-        mock = Mock(None)
-
-        td[obj] = mock
+    def testSetItemIsImplemented(self):
+        td = IdentityMap()
+        key = object()
+        val = object()
+        td[key] = val
 
     def testGetValueForKey(self):
-        td = _Dict()
-        obj = {}
-        mock = Mock(None)
-        td[obj] = mock
+        td = IdentityMap()
+        key = object()
+        val = object()
+        td[key] = val
 
-        assert td.get(obj) == mock
+        assert td.get(key) == val
+        assert td.get(object(), 42) == 42
 
     def testReplaceValueForSameKey(self):
-        td = _Dict()
-        obj = {}
-        mock1 = Mock(None)
-        mock2 = Mock(None)
-        td[obj] = mock1
-        td[obj] = mock2
+        td = IdentityMap()
+        key = object()
+        mock1 = object()
+        mock2 = object()
+        td[key] = mock1
+        td[key] = mock2
 
-        assert td.pop(obj) == mock2
-        with pytest.raises(KeyError):
-            td.pop(obj)
+        assert td.values() == [mock2]
 
     def testPopKey(self):
-        td = _Dict()
-        obj = {}
-        mock = Mock(None)
-        td[obj] = mock
-
-        assert td.pop(obj) == mock
-        assert td.get(obj) is None
-
-    def testIterValues(self):
-        td = _Dict()
-        obj = {}
-        mock = Mock(None)
-        td[obj] = mock
+        td = IdentityMap()
+        key = object()
+        val = object()
+        td[key] = val
 
-        assert td.values() == [mock]
+        assert td.pop(key) == val
+        assert td.values() == []
 
     def testClear(self):
-        td = _Dict()
-        obj = {}
-        mock = Mock(None)
-        td[obj] = mock
+        td = IdentityMap()
+        key = object()
+        val = object()
+        td[key] = val
 
         td.clear()
-        assert td.get(obj) is None
+        assert td.values() == []
 
+    def testEqualityIsIgnored(self):
+        td = IdentityMap()
+        td[{"one", "two", "foo"}] = object()
+        td[{"one", "two", "foo"}] = object()
+        assert len(td.values()) == 2