pytest で datetime.now() をモック化したい
pytest を使って datetime.now()
で現在日時を取得しているコードをモック化してテストする方法をまとめます。
datetime.now()
は実行時の現在日時を返すのでモック化しないとテストが難しい場合があります。ということでモック化したいですが datetime.now()
は組み込み関数(属性)のため単純にモック化できません。例えば pytest-mock
を使ってモック化すると、以下のようなエラーになります。
import datetime
def test_datetime_now(mocker):
# datetime.now() をモック化して固定値を返す
mocker.patch('datetime.datetime.now', return_value=datetime.datetime(2000, 1, 2, 3, 4))
# 現在日時を取得
now = datetime.now()
# 検証
assert now == datetime.datetime(2000, 1, 2, 3, 4)
# FAILED test_sample.py::test_datetime_now - TypeError: can't set attributes of built-in/extension type 'datetime.datetime'
組み込みの関数をモック化するのは許されないみたいです。
ということで別の方法を考えます。freezegun というプラグインを使って datetime
モジュールをモック化するのが簡単で広く使われているようです。
pytest-freezegun
$ pip install pytest-freezegun
pytest-freezegun
は datetime
モジュールをモック化する pytest プラグインです。
pytest-freezegun
をインストールすると、freezer
というフィクスチャや freeze_time
というマーカーが提供されます。
これらを使えば日時を固定したり変更したりできます。
@pytest.mark.freeze_time: テストメソッド内の datetime.now を固定する
マーカーを使ってテストメソッド内での日時を固定するコードです。テストメソッドが終了すれば、日時のモックは解除されます。
import datetime
import pytest
# datetime.now() を指定日時に固定する
@pytest.mark.freeze_time(datetime.datetime(2000, 1, 2, 3, 4))
def test_datetime_now():
# 現在日時を取得
now = datetime.datetime.now()
# 検証
assert now == datetime.datetime(2000, 1, 2, 3, 4)
freezer フィクスチャで datetime.now をモックしたり操作する
freezer
フィクスチャを使えば日時をテストメソッド内で固定したり、変更したりできます。
freezer.move_to()
で現在日時を指定の日時に固定できます。
import datetime
import pytest
def test_datetime(freezer):
# 日時を変更する
freezer.move_to(datetime.datetime(2000, 1, 2, 3, 4))
now = datetime.datetime.now()
assert now == datetime.datetime(2000, 1, 2, 3, 4)
# 3分後にする
freezer.move_to(datetime.datetime(2001, 1, 2, 3, 4) + datetime.timedelta(minutes=3))
now = datetime.datetime.now()
assert now == datetime.datetime(2001, 1, 2, 3, 4 + 3)
move_to
を複数回テスト中に実行すれば、好きなタイミングで好きな時刻に変更できます。
便利ですね。
MagicMock を使って datetime.now だけをモックする
pytest でも datetime.datetime.now() を mock したい! – Qiita
上記ページを参考にしてます。
MagicMock(wraps=datetime.datetime)
で datetime
モジュ。ルをラップしたモックオブジェクトを作成し、now()
の処理だけモック化する方法です。モックオブジェクトを作成したら、monkeypatch
で差し替えます。
import datetime
from unittest.mock import MagicMock
def test_datetime(monkeypatch):
# MagicMock を作成し、datetime.now() だけをモック化する
datetime_mock = MagicMock(wraps=datetime.datetime)
datetime_mock.now.return_value = datetime.datetime(2000, 1, 2, 3, 4)
# datetime モジュールをモックに差し替える
monkeypatch.setattr(datetime, "datetime", datetime_mock)
# 検証
now = datetime.datetime.now()
assert now == datetime.datetime(2000, 1, 2, 3, 4)
なにがしかの理由で freezegun
やその他プラグインが使えないときにはこの方法が便利そうです。
コメントを書く