datetime.now()
が返す値を固定化するのにハマったので、事象と解決策を残しておきます。
前提
- 環境
- python 3.8
- ライブラリ
- pytest
- 以下のコードをテストする
from datetime import datetime
def func():
return datetime.now()
失敗例
一番初めに思いつくのがdatetime.now()
に対してモックを当てることですが、これは失敗します。
from datetime import datetime
from unittest import mock
from sample import func
@mock.patch('app.sample.datetime.now')
def test_func(m_now):
m_now.return_value = datetime(2021, 2, 1)
actual = func()
assert actual == m_now()
...
E TypeError: can't set attributes of built-in/extension type 'datetime.datetime'
...
「datetime.datetime
の属性を勝手に変更しようとすんじゃねぇよ!!!!」と怒られました。
このようなエラーが発生する理由は、datetime
がイミュータブルなオブジェクトだからです。
イミュータブルなオブジェクトなので、メソッドを新しい関数(mock)に差し替えることができません。(参考:イミュータブルってなに?)
成功例
では、どうやってdatetime.now()
の値を固定化すればいいのか。
これはdatetime
オブジェクトの中身を弄るのではなく、datetime
オブジェクトごと差し替えてあげることで解決できます。
from datetime import datetime
from sample import func
@mock.patch('app.sample.datetime')
def test_func(m):
m.now.return_value = datetime(2021, 2, 1)
actual = func()
assert actual == m.now()
...
================== 1 passed in 1.23s ==================
最後に
モック化するときは、対象のオブジェクトがイミュータブルかどうかは注意しておきたいですね。
ちなみにpytestが一つも出てきてないですが、裏でテストを実行してくれてました。
また、pytestでmockを使うならpytest-mocker
などを調べてみるのも面白いです。
では。
参考
まだmockで消耗してるの?mockを理解するための3つのポイント
Trying to mock datetime.date.today(), but not working|stack overflow