Skip to content

Commit 4f592f7

Browse files
authored
Support patch (#3)
* add docs and tests for patch checks * add support for patch * increment version * add note re changed codes in changelog
1 parent 8a75f22 commit 4f592f7

File tree

6 files changed

+339
-20
lines changed

6 files changed

+339
-20
lines changed

CHANGELOG.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,26 @@
22

33
## [Unreleased]
44

5+
## [v1.2.0] - 2023-01-14
6+
7+
### Added
8+
9+
- Lint checks that ensure `patch` is called with any one or more of the `new`,
10+
`spec`, `spec_set`, `autospec` or `new_callable` arguments
11+
12+
### Fix
13+
14+
- Ensure that error codes are correctly mapped for `NonCallableMock` and
15+
`AsyncMock` which were mapped to the `MagicMock` code before
16+
17+
### Changed
18+
19+
- Changed codes for mock checks:
20+
- `Mock`: `TMS001` -> `TMS010`,
21+
- `MagicMock`: `TMS002` -> `TMS011`,
22+
- `NonCallableMock`: `TMS003` -> `TMS012` and
23+
- `AsyncMock`: `TMS004` -> `TMS013`.
24+
525
## [v1.1.0] - 2023-01-14
626

727
### Added
@@ -19,3 +39,4 @@
1939
[//]: # "Release links"
2040
[v1.0.0]: https://github.com/jdkandersson/flake8-mock-spec/releases/v1.0.0
2141
[v1.1.0]: https://github.com/jdkandersson/flake8-mock-spec/releases/v1.1.0
42+
[v1.2.0]: https://github.com/jdkandersson/flake8-mock-spec/releases/v1.2.0

README.md

Lines changed: 48 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ This will produce warnings such as:
3030

3131
```shell
3232
flake8 test_source.py
33-
test_source.py:5:22: TMS001 unittest.mock.Mock instances should be constructed with the spec or spec_set argument, more information: https://github.com/jdkandersson/flake8-mock-spec#fix-tms001
33+
test_source.py:5:22: TMS010 unittest.mock.Mock instances should be constructed with the spec or spec_set argument, more information: https://github.com/jdkandersson/flake8-mock-spec#fix-tms010
3434
```
3535

3636
This can be resolved by changing the code to:
@@ -49,16 +49,18 @@ def test_foo():
4949

5050
A few rules have been defined to allow for selective suppression:
5151

52-
* `TMS001`: checks that `unittest.mock.Mock` instances are constructed with the
52+
* `TMS010`: checks that `unittest.mock.Mock` instances are constructed with the
5353
`spec` or `spec_set` argument.
54-
* `TMS002`: checks that `unittest.mock.MagicMock` instances are constructed with
54+
* `TMS011`: checks that `unittest.mock.MagicMock` instances are constructed with
5555
the `spec` or `spec_set` argument.
56-
* `TMS003`: checks that `unittest.mock.NonCallableMock` instances are
56+
* `TMS012`: checks that `unittest.mock.NonCallableMock` instances are
5757
constructed with the `spec` or `spec_set` argument.
58-
* `TMS004`: checks that `unittest.mock.AsyncMock` instances are constructed
58+
* `TMS013`: checks that `unittest.mock.AsyncMock` instances are constructed
5959
with the `spec` or `spec_set` argument.
60+
* `TMS020`: checks that `unittest.mock.patch` is called with any one or more of
61+
the `new`, `spec`, `spec_set`, `autospec` or `new_callable` arguments
6062

61-
### Fix TMS001
63+
### Fix TMS010
6264

6365
This linting rule is triggered by creating a `unittest.mock.Mock` instance
6466
without the `spec` or `spec_set` argument. For example:
@@ -91,7 +93,7 @@ def test_foo():
9193
mocked_foo = mock.Mock(spec_set=Foo)
9294
```
9395

94-
### Fix TMS002
96+
### Fix TMS011
9597

9698
This linting rule is triggered by creating a `unittest.mock.MagicMock` instance
9799
without the `spec` or `spec_set` argument. For example:
@@ -124,7 +126,7 @@ def test_foo():
124126
mocked_foo = mock.MagicMock(spec_set=Foo)
125127
```
126128

127-
### Fix TMS003
129+
### Fix TMS012
128130

129131
This linting rule is triggered by creating a `unittest.mock.NonCallableMock`
130132
instance without the `spec` or `spec_set` argument. For example:
@@ -157,7 +159,7 @@ def test_foo():
157159
mocked_foo = mock.NonCallableMock(spec_set=Foo)
158160
```
159161

160-
### Fix TMS004
162+
### Fix TMS013
161163

162164
This linting rule is triggered by creating a `unittest.mock.AsyncMock` instance
163165
without the `spec` or `spec_set` argument. For example:
@@ -189,3 +191,40 @@ from foo import Foo
189191
def test_foo():
190192
mocked_foo = mock.AsyncMock(spec_set=Foo)
191193
```
194+
195+
### Fix TMS020
196+
197+
This linting rule is triggered by calling `unittest.mock.patch` without any one
198+
or more of the `new`, `spec`, `spec_set`, `autospec` or `new_callable`
199+
arguments. For example:
200+
201+
```Python
202+
from unittest import mock
203+
204+
@mock.patch("Foo")
205+
def test_foo():
206+
pass
207+
208+
with mock.patch("Foo") as mocked_foo:
209+
pass
210+
211+
foo_patcher = patch("Foo")
212+
```
213+
214+
This example can be fixed by using any one or more of the `new`, `spec`,
215+
`spec_set`, `autospec` or `new_callable` arguments:
216+
217+
```Python
218+
from unittest import mock
219+
220+
from foo import Foo
221+
222+
@mock.patch("Foo", spec=Foo)
223+
def test_foo():
224+
pass
225+
226+
with mock.patch("Foo", spec_set=Foo) as mocked_foo:
227+
pass
228+
229+
foo_patcher = patch("Foo", autospec=True)
230+
```

flake8_mock_spec.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,26 +19,41 @@
1919
f"%s unittest.mock.%s instances should be constructed with the {' or '.join(SPEC_ARGS)} "
2020
f"argument, {MORE_INFO_BASE}#fix-%s"
2121
)
22-
MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}001"
22+
MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}010"
2323
MOCK_SPEC_MSG = MOCK_SPEC_MSG_BASE % (MOCK_SPEC_CODE, MOCK_CLASS, MOCK_SPEC_CODE.lower())
24-
MAGIC_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}002"
24+
MAGIC_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}011"
2525
MAGIC_MOCK_SPEC_MSG = MOCK_SPEC_MSG_BASE % (
2626
MAGIC_MOCK_SPEC_CODE,
2727
MAGIC_MOCK_CLASS,
2828
MAGIC_MOCK_SPEC_CODE.lower(),
2929
)
30-
NON_CALLABLE_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}003"
30+
NON_CALLABLE_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}012"
3131
NON_CALLABLE_MOCK_SPEC_MSG = MOCK_SPEC_MSG_BASE % (
3232
NON_CALLABLE_MOCK_SPEC_CODE,
3333
NON_CALLABLE_MOCK_CLASS,
3434
NON_CALLABLE_MOCK_SPEC_CODE.lower(),
3535
)
36-
ASYNC_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}004"
36+
ASYNC_MOCK_SPEC_CODE = f"{ERROR_CODE_PREFIX}013"
3737
ASYNC_MOCK_SPEC_MSG = MOCK_SPEC_MSG_BASE % (
3838
ASYNC_MOCK_SPEC_CODE,
3939
ASYNC_MOCK_CLASS,
4040
ASYNC_MOCK_SPEC_CODE.lower(),
4141
)
42+
MOCK_MSG_LOOKUP = {
43+
MOCK_CLASS: MOCK_SPEC_MSG,
44+
MAGIC_MOCK_CLASS: MAGIC_MOCK_SPEC_MSG,
45+
NON_CALLABLE_MOCK_CLASS: NON_CALLABLE_MOCK_SPEC_MSG,
46+
ASYNC_MOCK_CLASS: ASYNC_MOCK_SPEC_MSG,
47+
}
48+
49+
# The attribute actually does exist, mypy reports that it doesn't
50+
PATCH_FUNCTION: str = mock.patch.__name__ # type: ignore
51+
PATCH_ARGS = frozenset(("new", "spec", "spec_set", "autospec", "new_callable"))
52+
PATCH_CODE = f"{ERROR_CODE_PREFIX}020"
53+
PATCH_MSG = (
54+
f"{PATCH_CODE} unittest.mock.{PATCH_FUNCTION} should be called with any of the "
55+
f"{', '.join(PATCH_ARGS)} arguments, {MORE_INFO_BASE}#fix-{PATCH_CODE.lower()}"
56+
)
4257

4358

4459
class Problem(NamedTuple):
@@ -88,10 +103,16 @@ def visit_Call(self, node: ast.Call) -> None: # pylint: disable=invalid-name
88103
Problem(
89104
lineno=node.lineno,
90105
col_offset=node.col_offset,
91-
msg=MOCK_SPEC_MSG if name == MOCK_CLASS else MAGIC_MOCK_SPEC_MSG,
106+
msg=MOCK_MSG_LOOKUP[name],
92107
)
93108
)
94109

110+
if name is not None and name == PATCH_FUNCTION:
111+
if not any(keyword.arg in PATCH_ARGS for keyword in node.keywords):
112+
self.problems.append(
113+
Problem(lineno=node.lineno, col_offset=node.col_offset, msg=PATCH_MSG)
114+
)
115+
95116
# Ensure recursion continues
96117
self.generic_visit(node)
97118

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[tool.poetry]
22
name = "flake8-mock-spec"
3-
version = "1.1.0"
3+
version = "1.2.0"
44
description = "A linter that checks mocks are constructed with the spec argument"
55
authors = ["David Andersson <[email protected]>"]
66
license = "Apache 2.0"

tests/test_flake8_mock_spec_integration.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,14 @@
88

99
import pytest
1010

11-
from flake8_mock_spec import MAGIC_MOCK_SPEC_CODE, MOCK_SPEC_CODE, MOCK_SPEC_MSG
11+
from flake8_mock_spec import (
12+
ASYNC_MOCK_SPEC_CODE,
13+
MAGIC_MOCK_SPEC_CODE,
14+
MOCK_SPEC_CODE,
15+
MOCK_SPEC_MSG,
16+
NON_CALLABLE_MOCK_SPEC_CODE,
17+
PATCH_CODE,
18+
)
1219

1320

1421
def test_help():
@@ -87,6 +94,30 @@ def test_fail(tmp_path: Path):
8794
""",
8895
id=f"{MAGIC_MOCK_SPEC_CODE} disabled",
8996
),
97+
pytest.param(
98+
f"""
99+
from unittest import mock
100+
101+
mock.NonCallableMock() # noqa: {NON_CALLABLE_MOCK_SPEC_CODE}
102+
""",
103+
id=f"{NON_CALLABLE_MOCK_SPEC_CODE} disabled",
104+
),
105+
pytest.param(
106+
f"""
107+
from unittest import mock
108+
109+
mock.AsyncMock() # noqa: {ASYNC_MOCK_SPEC_CODE}
110+
""",
111+
id=f"{ASYNC_MOCK_SPEC_CODE} disabled",
112+
),
113+
pytest.param(
114+
f"""
115+
from unittest import mock
116+
117+
mock.patch() # noqa: {PATCH_CODE}
118+
""",
119+
id=f"{PATCH_CODE} disabled",
120+
),
90121
],
91122
)
92123
def test_pass(code: str, tmp_path: Path):

0 commit comments

Comments
 (0)