pytest-mockのよく使う実装方法をまとめてみた

pytest-mockの使い方を調べたのでメモ
今後も使いそうなユースケースを都度調べるのが面倒なのでまとめてみました。
pytestを調べてから1日で書いた記事なので色々過不足があると思いますが、もっと良い方法が見つかったら随時アップデートしていきたいと思います。

# pytest、pytest-mockについて
pythonのテストコードを実装するときに使うライブラリの1つです。
詳細は公式ホームページを参照してください。

## pytest
https://docs.pytest.org/en/latest/index.html

## pytest-mock
https://pypi.org/project/pytest-mock/

# 説明に使うメソッド

下記のhogeメソッドをテスト対象とします。

```python
from mailer import Mailer

# messagesの例
# ['1通目の本文', '2通目の本文']
def hoge(messages):
    try:
        # messagesの数だけメール送信
        for message in messages:
          has_error = Mailer.send(message)
          if has_error:
            # エラー発生時は1を返却
            return 1

        # 正常終了時は0を返却
        return 0
    except Exception as e:
        # 例外発生時は2を返却
        return 2
```

この記事を書いたときに使ったバージョンは下記の通りです。

* python 3.8.5
* pytest 6.1.1
* pytest-mock 3.3.1

# `Mailer.send`をモックに差し替える

```python:test_hoge.py
from hoge import hoge
from mailer import Mailer
from unittest.mock import call

def test_hoge(mocker):
    # Mailer.sendをモックに差し替える
    mailer_mock = mocker.patch.object(Mailer, 'send')

    messages = ['1通目の本文', '2通目の本文']

    # 上記でMailer.sendをモックにしているのでhogeを実行しても本物のMailer.sendは実行されません
    assert hoge(messages) == 0
```

# 呼び出し回数を検証したい

```python:test_hoge.py
from hoge import hoge
from mailer import Mailer
from unittest.mock import call

def test_hoge(mocker):
    # Mailer.sendをモックに差し替える
    mailer_mock = mocker.patch.object(Mailer, 'send')

    messages = ['1通目の本文', '2通目の本文']
    assert hoge(messages) == 0

    # Mailer.sendが2回呼ばれることを確認できます
    assert mailer_mock.call_count == 2
```

# 呼び出されないことを検証したい

```python:test_hoge.py
from hoge import hoge
from mailer import Mailer
from unittest.mock import call

def test_hoge(mocker):
    # Mailer.sendをモックに差し替える
    mailer_mock = mocker.patch.object(Mailer, 'send')

    # messagesが空なのでMailer．sendは呼ばれないようにする
    messages = []
    assert hoge(messages) == 0

    # Mailer.sendが呼ばれないことを検証
    mailer_mock.assert_not_called()
```


# パラメーターを検証したい

```python:test_hoge.py
from hoge import hoge
from mailer import Mailer
from unittest.mock import call

def test_hoge(mocker):
    # Mailer.sendをモックに差し替える
    mailer_mock = mocker.patch.object(Mailer, 'send')

    messages = ['1通目の本文', '2通目の本文']
    assert hoge(messages) == 0

    # 1回目は'1通目の本文'、2回目は'2通目の本文'がパラメーターで渡されて呼び出されていることが検証できます
    mailer_mock.assert_has_calls([call('1通目の本文'), call('2通目の本文')])
```

# 戻り値を制御したい

```python:test_hoge.py
from hoge import hoge
from mailer import Mailer
from unittest.mock import call

def test_hoge(mocker):
    # side_effectに配列で戻り値のリストを渡します
    # 呼び出されるごとに配列の先頭から順番に返却されます
    # この例の場合、1回目はFalse、2回目はTrueが返却されます
    mailer_mock = mocker.patch.object(Mailer, 'send', side_effect = [False, True])

    messages = ['1通目の本文', '2通目の本文']

    # 2回めのMailer.sendがTrueを返却するので戻り値1の検証ができます
    assert hoge(messages) == 1
```

# 例外を発生させたい

```python:test_hoge.py
from hoge import hoge
from mailer import Mailer
from unittest.mock import call

def test_hoge(mocker):
    # side_effectに例外を指定します
    # この例の場合、1回目はFalse、2回目は例外が発生します
    mailer_mock = mocker.patch.object(Mailer, 'send', side_effect = [False, Exception('error')])

    messages = ['1通目の本文', '2通目の本文']

    # 2回めのMailer.sendが例外が発生するので戻り値2の検証ができます
    assert hoge(messages) == 2
```