Repository: ESSS/oop-ext

Issue:
Bug with mocked `classmethod`
If you mock a `classmethod`, `unittest.mock` creates a `MagickMock` object instead of a method, that makes the internal `_IsMethod` check fail. 
The following test fails:
```python
def testRequiresDeclarationBug(mocker):
    class IFoo(Interface):
        @classmethod
        def foo(cls):
            ""

    @ImplementsInterface(IFoo)
    class FooImplementation:
        @classmethod
        def foo(cls):
            ""

    mock = mocker.spy(FooImplementation, "foo")
    AssertImplements(FooImplementation, IFoo, requires_declaration=True)
```
Output:
```
AssertionError: Method 'foo' is missing in class 'FooImplementation' (required by interface 'IFoo')
```


Discussion:
codecov[bot]: # [Codecov](https://codecov.io/gh/ESSS/oop-ext/pull/21?src=pr&el=h1) Report
> Merging [#21](https://codecov.io/gh/ESSS/oop-ext/pull/21?src=pr&el=desc) into [master](https://codecov.io/gh/ESSS/oop-ext/commit/00000f0c728003d078fa48a7a0b121725326d491?src=pr&el=desc) will **increase** coverage by `0.04%`.
> The diff coverage is `100%`.


```diff
@@            Coverage Diff             @@
##           master      #21      +/-   ##
==========================================
+ Coverage   95.52%   95.56%   +0.04%     
==========================================
  Files          32       32              
  Lines        3215     3225      +10     
==========================================
+ Hits         3071     3082      +11     
+ Misses        144      143       -1
```


Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index f98b109..41e5a14 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -1,6 +1,11 @@
-0.5.0 (2019-12-12)
+0.5.1 (2019-12-20)
 ==================
 
+* Fixes an issue (#20) where mocked `classmethods` weren't considered a valid method during internal checks.
+
+0.5.0 (2019-12-12)
+------------------
+
 * Add optional argument ``requires_declaration`` so users can decide whether or not ``@ImplementsInterface`` declarations are necessary.
 
 0.4.0 (2019-12-03)
diff --git a/src/oop_ext/interface/_interface.py b/src/oop_ext/interface/_interface.py
index c84ffa2..5fd1ffa 100644
--- a/src/oop_ext/interface/_interface.py
+++ b/src/oop_ext/interface/_interface.py
@@ -590,10 +590,13 @@ def _IsMethod(member):
             3) instances of Method (should it be implementors of "IMethod"?)
 
     """
+    from unittest import mock
+
     return (
         inspect.isfunction(member)
         or inspect.ismethod(member)
         or isinstance(member, Method)
+        or isinstance(member, mock.MagicMock)
     )
 
 


Test patch:
diff --git a/src/oop_ext/interface/_tests/test_interface.py b/src/oop_ext/interface/_tests/test_interface.py
index 60bcc10..8beb611 100644
--- a/src/oop_ext/interface/_tests/test_interface.py
+++ b/src/oop_ext/interface/_tests/test_interface.py
@@ -814,11 +814,13 @@ def test_interface_subclass_mocked(mocker, check_before, autospec):
         pass
 
     if check_before:
-        interface.IsImplementation(Bar, II)
+        interface.IsImplementation(Bar, II, requires_declaration=False)
+        interface.IsImplementation(Bar, II, requires_declaration=True)
 
     mocker.patch.object(Foo, "foo", autospec=autospec)
 
-    assert interface.IsImplementation(Bar, II) == (autospec or check_before)
+    assert interface.IsImplementation(Bar, II, requires_declaration=False)
+    assert interface.IsImplementation(Bar, II, requires_declaration=True)
 
 
 def testErrorOnInterfaceDeclaration():
@@ -882,3 +884,23 @@ def testIsImplementationOfAny():
     AssertImplements(a_obj, _InterfM3)
     assert IsImplementationOfAny(A, [_InterfM1, _InterfM2, _InterfM3, _InterfM4])
     assert not IsImplementationOfAny(A, [_InterfM1, _InterfM2, _InterfM4])
+
+
+def testClassMethodBug(mocker):
+    """
+    Issue #20
+    """
+
+    class IFoo(Interface):
+        @classmethod
+        def foo(cls):
+            ""
+
+    @ImplementsInterface(IFoo)
+    class FooImplementation:
+        @classmethod
+        def foo(cls):
+            ""
+
+    mocker.spy(FooImplementation, "foo")
+    AssertImplements(FooImplementation, IFoo, requires_declaration=True)