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
```

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)
     )
 
 

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')
```

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)

pip install -e .[testing]
parse_log_pytest
requirements.txt
pytest pytest-cov pytest-mock
apt-get update apt-get install -y gcc
3.9
requirements/base.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
cachetools==5.5.2
certifi==2025.1.31
cfgv==3.4.0
chardet==5.2.0
charset-normalizer==3.4.1
codecov==2.1.13
colorama==0.4.6
coverage==7.8.0
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.18.0
identify==2.6.9
idna==3.10
iniconfig==2.1.0
nodeenv==1.9.1
-e git+https://github.com/ESSS/oop-ext.git@00000f0c728003d078fa48a7a0b121725326d491#egg=oop_ext
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
pre_commit==4.2.0
pyproject-api==1.9.0
pytest==8.3.5
pytest-cov==6.0.0
pytest-mock==3.14.0
PyYAML==6.0.2
requests==2.32.3
tomli==2.2.1
tox==4.25.0
typing_extensions==4.13.0
urllib3==2.3.0
virtualenv==20.29.3

name: oop-ext
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cachetools==5.5.2
      - certifi==2025.1.31
      - cfgv==3.4.0
      - chardet==5.2.0
      - charset-normalizer==3.4.1
      - codecov==2.1.13
      - colorama==0.4.6
      - coverage==7.8.0
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - identify==2.6.9
      - idna==3.10
      - iniconfig==2.1.0
      - nodeenv==1.9.1
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pre-commit==4.2.0
      - pyproject-api==1.9.0
      - pytest==8.3.5
      - pytest-cov==6.0.0
      - pytest-mock==3.14.0
      - pyyaml==6.0.2
      - requests==2.32.3
      - tomli==2.2.1
      - tox==4.25.0
      - typing-extensions==4.13.0
      - urllib3==2.3.0
      - virtualenv==20.29.3
prefix: /opt/conda/envs/oop-ext

src/oop_ext/interface/_tests/test_interface.py::test_interface_subclass_mocked[False-False] src/oop_ext/interface/_tests/test_interface.py::testClassMethodBug
src/oop_ext/interface/_tests/test_interface.py::testBasics src/oop_ext/interface/_tests/test_interface.py::testMissingMethod src/oop_ext/interface/_tests/test_interface.py::testSubclasses src/oop_ext/interface/_tests/test_interface.py::testSubclasses2 src/oop_ext/interface/_tests/test_interface.py::testNoName src/oop_ext/interface/_tests/test_interface.py::testAttributes src/oop_ext/interface/_tests/test_interface.py::testPassNoneInAssertImplementsFullChecking src/oop_ext/interface/_tests/test_interface.py::testNoCheck src/oop_ext/interface/_tests/test_interface.py::testCallbackAndInterfaces src/oop_ext/interface/_tests/test_interface.py::testInterfaceStubFromClasses src/oop_ext/interface/_tests/test_interface.py::testStubsFromInstances src/oop_ext/interface/_tests/test_interface.py::testIsImplementationWithSubclasses src/oop_ext/interface/_tests/test_interface.py::testIsImplementationWithBuiltInObjects src/oop_ext/interface/_tests/test_interface.py::testClassImplementMethod src/oop_ext/interface/_tests/test_interface.py::testGetImplementedInterfaces src/oop_ext/interface/_tests/test_interface.py::testGetImplementedInterfaces2 src/oop_ext/interface/_tests/test_interface.py::testAdaptableInterface src/oop_ext/interface/_tests/test_interface.py::testNull src/oop_ext/interface/_tests/test_interface.py::testSetItem src/oop_ext/interface/_tests/test_interface.py::testAssertImplementsDoesNotDirObject src/oop_ext/interface/_tests/test_interface.py::testImplementorWithAny src/oop_ext/interface/_tests/test_interface.py::testInterfaceCheckRequiresInterface src/oop_ext/interface/_tests/test_interface.py::testReadOnlyAttribute src/oop_ext/interface/_tests/test_interface.py::testReadOnlyAttributeMissingImplementation src/oop_ext/interface/_tests/test_interface.py::testImplementsTwice src/oop_ext/interface/_tests/test_interface.py::testDeclareClassImplements src/oop_ext/interface/_tests/test_interface.py::testCallableInterfaceStub src/oop_ext/interface/_tests/test_interface.py::testImplementsInterfaceAsBoolError src/oop_ext/interface/_tests/test_interface.py::test_interface_subclass_mocked[True-True] src/oop_ext/interface/_tests/test_interface.py::test_interface_subclass_mocked[True-False] src/oop_ext/interface/_tests/test_interface.py::test_interface_subclass_mocked[False-True] src/oop_ext/interface/_tests/test_interface.py::testErrorOnInterfaceDeclaration src/oop_ext/interface/_tests/test_interface.py::testKeywordOnlyArguments src/oop_ext/interface/_tests/test_interface.py::testHashableArgumentsInterface src/oop_ext/interface/_tests/test_interface.py::testHashableArgumentsImplementation src/oop_ext/interface/_tests/test_interface.py::testIsImplementationOfAny
CHANGELOG.rst src/oop_ext/interface/_interface.py
CHANGELOG.rst src/oop_ext/interface/_interface.py