インストール
poetryを使っていれば簡単です。
poetry add -D pytest-mock
使っていなくても簡単です。けれど、poetryのほうがおすすめです。
pip install pytest-mock
```
# テストの書き方
テストメソッドの引数として`mocker`を指定します。
```python
class MyClassTests:
def test_something(self, mocker):
pass
```
# モックの作り方
[モック - Mock](https://docs.python.org/ja/3/library/unittest.mock.html#the-mock-class)を作るには、`mocker.Mock`を使ってインスタンスを生成するやり方と、`mocker.patch`を使ってダイナミックにMockインスタンスに置き換えていくやり方があります。
題材として、このようなモジュールがあったとします。
```
exmock
├── __init__.py
├── bar.py
├── foo.py
└── target.py
```
```python:exmock/target.py
from .foo import FooClass
from .bar import BarClass
class TargetClass:
def do_something(self, foo: FooClass) -> None:
""" do_foo()の結果をdo_bar()に渡します。 """
bar = BarClass()
bar.do_bar(foo.do_foo())
```
## クラスのモックを作る
```python
foo_mock = mocker.Mock(spec=FooClass)
# foo_mock.do_foo もモックになっています。
```
specとしてクラスを指定することで、クラスに実在する属性のみが生えるようにします。
生えてきた属性も`Mock`のインスタンスです。
## コンストラクタの戻り値をモックにする
コンストラクタは値を返さないのです表現としておかしいですが、説明の都合上このようにしています。
```python
bar_mock = mocker.Mock(spec=BarClass)
mocker.patch('exmock.target.BarClass', return_value=bar_mock)
```
テスト対象ファイル**exmock/target.py**にある`from .bar import BarClass`にあわせて、`exmock.target.BarClass`をパッチしています。
## メソッドをモック化する
```python
do_bar_mock = mocker.patch.object(BarClass, 'do_bar')
```
`do_bar`メソッドの実装がモック化し、なにもしなくなります。
`do_bar`メソッドの動作を変更するには、戻り値のモックを使います。
# モックの動作を変更する
生成したモックは、テストにあわせて任意の動きをさせます。
## 戻り値を設定する
生成したモックの`return_value`属性に値をセットします。
```python
mymock.return_value = 戻り値
```
## プロパティの戻り値を設定する
直接値をセットすればOKです。
```python
mymock.foo = 戻り値
```
## 呼び出されたときに例外を発生させる
生成したモックの`side_effect`属性に例外をセットします。
```python
mymock.side_effect = Exception('エラーです')
```
## まるっと置き換える
モック対象の実装をまるっと置き換えます。
```python
def side_effect():
pass
mymock.side_effect = side_effect
```
# モックのテスト
モックに対して、何らかの操作が行われたことをテストします。
## 1回呼び出されたことをテストする
```python
mymock.assert_called_once()
```
## 複数回呼び出されたことをテストする
```python
# pytestを使っている場合です。
assert mymock.call_count == 回数
```
## まったく呼び出されないことをテストする
```python
mymock.assert_not_called()
```
## 特定引数で呼び出されたことをテストする
```python
mymock.assert_called_with(特定引数1, 特定引数2, ……)
mymock.assert_called_once_with(特定引数1, 特定引数2, ……) # 1回呼び出される場合
```
## 複雑な引数をテストする
```python
args, kwargs = mymock.call_args
# argsやkwargsをテストする
```
`call_args`に、直近の呼び出し引数が入ってます。
`args`は、キーワードなし引数が入っているタプルです。`kwargs`は、キーワード付き引数が入っているディクショナリです。
# 検証環境
この記事を書くにあたって使った環境です。
* Python 3.8.2
* Poetry 1.1.4
* pytest-mock 3.4.0
使ったファイルはこちらに置きました。
https://github.com/sengokyu/python-pytest-mock-usage-example