TL; DR
こんな感じでモックすると、datetime.datetime.now()
をモックできます。この方法だと datetime.datetime
の他の関数はそのまま使うことができます。
from unittest.mock import MagicMock
def test_mocking_datetime_now(monkeypatch):
datetime_mock = MagicMock(wraps=datetime.datetime)
datetime_mock.now.return_value = datetime.datetime(2020, 3, 11, 0, 0, 0)
monkeypatch.setattr(datetime, "datetime", datetime_mock)
背景
現在時刻を取得する処理をテストするときに、datetime.datetime.now()
をモックしたいことがあります。このとき、単純に monkeypatch.setattr
でモックしようとしても、datetime.datetime.now()
は built-in のため、モックすることができません。
import datetime
from unittest.mock import MagicMock
FAKE_NOW = datetime.datetime(2020, 3, 11, 0, 0, 0)
def test_mocking_datetime_now_incorrect(monkeypatch):
monkeypatch.setattr(datetime.datetime, "now", MagicMock(return_value=FAKE_NOW)
> monkeypatch.setattr(datetime.datetime, "now", MagicMock(return_value="hoge"))
E TypeError: can't set attributes of built-in/extension type 'datetime.datetime'
そこで MagicMock(wraps=datetime.datetime)
そこで、MagicMock(wraps=...)
を利用します。wrap 引数にモックするオブジェクトを指定して、通常のメソッドを wrap したオブジェクトに流しつつ、必要なメソッドをモックすることができます。
from unittest.mock import MagicMock
def test_mocking_datetime_now(monkeypatch):
# now() だけをモックした datetime_mock を作成
datetime_mock = MagicMock(wraps=datetime.datetime)
datetime_mock.now.return_value = datetime.datetime(2020, 3, 11, 0, 0, 0) # これで now() をモック
# datetime.datetime を datetime_mock で置き換え
monkeypatch.setattr(datetime, "datetime", datetime_mock)
# 以下、datetime.datetime.now() を使うテスト
余録
stackoverflow のトップ回答 がイケてなかったのでムシャクシャして(回答して)やった。反省はしてない。
トップ回答の方法だと datetime.datetime の他のメソッドが呼べないんですよね。こちらの方法なら、datetime.datetime.fromisoformat()
とか、他のメソッドは通常通りに呼べるぞ、と。
ちなみに別解としては、 pytest-freezegun
を使うというのがあり、こっちのほうがメジャーっぽいですね。参考: Pytest現在時刻テスト(日時固定) - Qiita