Pythonのmockについての備忘録

先日PythonのMockに悩まされまくったので備忘録を置いておきます。


##とりあえず公式ドキュメント
[unittest.mock --- モックオブジェクトライブラリ]
(https://docs.python.org/ja/3/library/unittest.mock.html)
[unittest.mock --- 入門]
(https://docs.python.org/ja/3/library/unittest.mock-examples.html)

2時間ぐらい悩んだことが公式にさらっと書いてあった。
公式は偉大


##requests.getをmockする

```tori01.py
import requests

def get_qiita(url):
    response = requests.get(url)
    return response
```

urlでgetして返ってきたresponseを返すだけ
<br>

```test_tori01.py
import unittest
from mock import Mock, patch

import tori01

class TestTori01(unittest.TestCase):

    @patch("tori01.requests.get")
    def test_get_response(self, requests_get):
        requests_get.return_value.status_code = 200

        res = tori01.get_response("mock.url")
        self.assertEqual(res.status_code, 200)
```

ステータスコードでアサーションチェック
今回これらのコードを土台とする


##patch周り

`tori01.py`で`requests`がimportされているので
`@patch("tori01.requests.get")`でpatchされる

これが`tori01.py`で`requests.get`がimportされていると
`@patch("tori01.get")`としないとpatchされない

```tori01.py
# import requests から変更
from requests import get

def get_response(url):
    response = get(url)
    return response
```

```test_tori01.py
import unittest
from mock import Mock, patch

import tori01

class TestTori01(unittest.TestCase):

    # @patch("tori01.requests.get") から変更
    @patch("tori01.get")
    def test_get_response(self, requests_get):
        requests_get.return_value.status_code = 200

        res = tori01.get_response("mock.url")
        self.assertEqual(res.status_code, 200)
```

<br>
`requests.get`ではなく、`requests`をmockしたい場合（どっちが正統なんだろうか）

```test_tori01.py
import unittest
from mock import Mock, patch

import tori01

class TestTori01(unittest.TestCase):

    # @patch("tori01.requests.get") から変更
    # 引数の命名もrequests.get→requestsに変更
    @patch("tori01.requests")
    def test_get_response(self, requests):
        # requests_get.return_value.status_code = 200 から変更
        requests.get.return_value.status_code = 200

        res = tori01.get_response("mock.url")
        self.assertEqual(res.status_code, 200)
```


##関数を呼び出す

呼び出された時の引数を確認することができる。

```test_tori01.py
import unittest
from mock import Mock, patch

import tori01

class TestTori01(unittest.TestCase):

    @patch("tori01.requests.get")
    def test_get_response(self, requests_get):

        # mockを返す関数を作り、呼び出されるようにする。
        def requests_get_mock(url):
            return Mock(status_code = 200, url = url)

        requests_get.side_effect = requests_get_mock

        res = tori01.get_response("mock.url")
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res.url, "mock.url")
```

<br>
こんな回りくどいことをしなくても`assert_called_with`でチェックできる。
他のassert系も色々ある


```test_tori01.py
import unittest
from mock import Mock, patch

import tori01

class TestTori01(unittest.TestCase):

    @patch("tori01.requests.get")
    def test_get_response(self, requests_get):
        requests_get.return_value.status_code = 200

        res = tori01.get_response("mock.url")
        self.assertEqual(res.status_code, 200)
        requests_get.assert_called_with("mock.url") #追加
```

<br>
引数によって戻り値を変化

```test_tori01.py
import unittest
from mock import Mock, patch

import tori01

class TestTori01(unittest.TestCase):

    @patch("tori01.requests.get")
    def test_get_response(self, requests_get):

        vals = {"mock.url":200, "error_mock.url":404}
        def requests_get_mock(val):
            return Mock(status_code = vals[val])

        requests_get.side_effect = requests_get_mock

        res = tori01.get_response("mock.url")
        res_error = tori01.get_response("error_mock.url")
        self.assertEqual(res.status_code, 200)
        self.assertEqual(res_error.status_code, 404)
```

<br>
例外を投げることもできる

```test_tori01.py
import unittest
import requests
from mock import Mock, patch

import tori01

class TestTori01(unittest.TestCase):

    @patch("tori01.requests.get")
    def test_get_response(self, requests_get):

        # side_effectに例外クラスを設定すれば例外が投げられる
        requests_get.side_effect = requests.HTTPError

        with self.assertRaises(requests.HTTPError):
            tori01.get_response("mock.url")
```

##追記したり、その2を書くかも

MagicMockとか…