Skip to content

Commit

Permalink
add support for NonCallableMock and AsyncMock (#2)
Browse files Browse the repository at this point in the history
* add support for NonCallableMock and AsyncMock
* add check that the class names being checked for are mock classes
* switch to using class names
  • Loading branch information
jdkandersson authored Jan 14, 2023
1 parent ef88d62 commit 3034820
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 6 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@

## [Unreleased]

## [v1.1.0] - 2023-01-14

### Added

- Lint checks that ensure `NonCallableMock` and `AsyncMock` constructors have
the `spec` or `spec_set` argument

## [v1.0.0] - 2023-01-14

### Added

- Lint checks that ensure `Mock` and `MagicMock` constructors have the `spec`
argument
or `spec_set`argument

[//]: # "Release links"
[v1.0.0]: https://github.com/jdkandersson/flake8-mock-spec/releases/v1.0.0
[v1.1.0]: https://github.com/jdkandersson/flake8-mock-spec/releases/v1.1.0
70 changes: 70 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,10 @@ A few rules have been defined to allow for selective suppression:
`spec` or `spec_set` argument.
* `TMS002`: checks that `unittest.mock.MagicMock` instances are constructed with
the `spec` or `spec_set` argument.
* `TMS003`: checks that `unittest.mock.NonCallableMock` instances are
constructed with the `spec` or `spec_set` argument.
* `TMS004`: checks that `unittest.mock.AsyncMock` instances are constructed
with the `spec` or `spec_set` argument.

### Fix TMS001

Expand Down Expand Up @@ -119,3 +123,69 @@ from foo import Foo
def test_foo():
mocked_foo = mock.MagicMock(spec_set=Foo)
```

### Fix TMS003

This linting rule is triggered by creating a `unittest.mock.NonCallableMock`
instance without the `spec` or `spec_set` argument. For example:

```Python
from unittest import mock

def test_foo():
mocked_foo = mock.NonCallableMock()
```

This example can be fixed by using the `spec` or `spec_set` argument in the
constructor:

```Python
from unittest import mock

from foo import Foo

def test_foo():
mocked_foo = mock.NonCallableMock(spec=Foo)
```

```Python
from unittest import mock

from foo import Foo

def test_foo():
mocked_foo = mock.NonCallableMock(spec_set=Foo)
```

### Fix TMS004

This linting rule is triggered by creating a `unittest.mock.AsyncMock` instance
without the `spec` or `spec_set` argument. For example:

```Python
from unittest import mock

def test_foo():
mocked_foo = mock.AsyncMock()
```

This example can be fixed by using the `spec` or `spec_set` argument in the
constructor:

```Python
from unittest import mock

from foo import Foo

def test_foo():
mocked_foo = mock.AsyncMock(spec=Foo)
```

```Python
from unittest import mock

from foo import Foo

def test_foo():
mocked_foo = mock.AsyncMock(spec_set=Foo)
```
21 changes: 18 additions & 3 deletions flake8_mock_spec.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@

import ast
from typing import Iterator, NamedTuple
from unittest import mock

MOCK_CLASS = "Mock"
MAGIC_MOCK_CLASS = "MagicMock"
MOCK_CLASSES = frozenset((MOCK_CLASS, MAGIC_MOCK_CLASS))
MOCK_CLASS: str = mock.Mock.__name__
MAGIC_MOCK_CLASS: str = mock.MagicMock.__name__
NON_CALLABLE_MOCK_CLASS: str = mock.NonCallableMock.__name__
ASYNC_MOCK_CLASS: str = mock.AsyncMock.__name__
MOCK_CLASSES = frozenset((MOCK_CLASS, MAGIC_MOCK_CLASS, NON_CALLABLE_MOCK_CLASS, ASYNC_MOCK_CLASS))
SPEC_ARGS = frozenset(("spec", "spec_set"))

ERROR_CODE_PREFIX = "TMS"
Expand All @@ -24,6 +27,18 @@
MAGIC_MOCK_CLASS,
MAGIC_MOCK_SPEC_CODE.lower(),
)
NON_CALLABLE_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}003"
NON_CALLABLE_MOCK_SPEC_MSG = MOCK_SPEC_MSG_BASE % (
NON_CALLABLE_MOCK_SPEC_CODE,
NON_CALLABLE_MOCK_CLASS,
NON_CALLABLE_MOCK_SPEC_CODE.lower(),
)
ASYNC_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}004"
ASYNC_MOCK_SPEC_MSG = MOCK_SPEC_MSG_BASE % (
ASYNC_MOCK_SPEC_CODE,
ASYNC_MOCK_CLASS,
ASYNC_MOCK_SPEC_CODE.lower(),
)


class Problem(NamedTuple):
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "flake8-mock-spec"
version = "1.0.0"
version = "1.1.0"
description = "A linter that checks mocks are constructed with the spec argument"
authors = ["David Andersson <david@jdkandersson.com>"]
license = "Apache 2.0"
Expand Down
58 changes: 57 additions & 1 deletion tests/test_flake8_mock_spec_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@
from __future__ import annotations

import ast
from unittest import mock

import pytest

from flake8_mock_spec import MAGIC_MOCK_SPEC_MSG, MOCK_SPEC_MSG, Plugin
from flake8_mock_spec import MAGIC_MOCK_SPEC_MSG, MOCK_CLASSES, MOCK_SPEC_MSG, Plugin


def _result(code: str) -> tuple[str, ...]:
Expand Down Expand Up @@ -43,6 +44,20 @@ def _result(code: str) -> tuple[str, ...]:
),
pytest.param(
"""
NonCallableMock()
""",
(f"2:0 {MAGIC_MOCK_SPEC_MSG}",),
id="module NonCallableMock no spec",
),
pytest.param(
"""
AsyncMock()
""",
(f"2:0 {MAGIC_MOCK_SPEC_MSG}",),
id="module AsyncMock no spec",
),
pytest.param(
"""
mock.Mock()
""",
(f"2:0 {MOCK_SPEC_MSG}",),
Expand Down Expand Up @@ -128,6 +143,34 @@ def test_():
),
pytest.param(
"""
NonCallableMock(spec=1)
""",
(),
id="module NonCallableMock spec",
),
pytest.param(
"""
NonCallableMock(spec_set=1)
""",
(),
id="module NonCallableMock spec_set",
),
pytest.param(
"""
AsyncMock(spec=1)
""",
(),
id="module AsyncMock spec",
),
pytest.param(
"""
AsyncMock(spec_set=1)
""",
(),
id="module AsyncMock spec_set",
),
pytest.param(
"""
Other()
""",
(),
Expand All @@ -149,3 +192,16 @@ def test_plugin(code: str, expected_result: tuple[str, ...]):
then: the expected result is returned
"""
assert _result(code) == expected_result


@pytest.mark.parametrize(
"class_", [pytest.param(class_, id=f"{class_} class") for class_ in MOCK_CLASSES]
)
def test_mock_classes_exist(class_: str):
"""
given: mock class
when: the existence of the class on unittest.mock is checked
then: the class exists in mock and can be instantiated
"""
assert hasattr(mock, class_)
getattr(mock, class_)()

0 comments on commit 3034820

Please sign in to comment.