web-dev-qa-db-ja.com

Pythonのdatetime.datetime.nowをpy.testでモンキーパッチする方法は?

datetime.datetime.now()を使用する関数をテストする必要があります。これを行う最も簡単な方法は何ですか?

25
sashk

Datetime.now関数をモンキーパッチする必要があります。以下の例では、後で他のテストで再利用できるフィクスチャを作成しています。

import datetime
import pytest

FAKE_TIME = datetime.datetime(2020, 12, 25, 17, 5, 55)

@pytest.fixture
def patch_datetime_now(monkeypatch):

    class mydatetime:
        @classmethod
        def now(cls):
            return FAKE_TIME

    monkeypatch.setattr(datetime, 'datetime', mydatetime)


def test_patch_datetime(patch_datetime_now):
    assert datetime.datetime.now() == FAKE_TIME
33
sashk

freezegun module があります:

_from datetime import datetime
from freezegun import freeze_time # $ pip install freezegun

@freeze_time("Jan 14th, 2012")
def test_Nice_datetime():
    assert datetime.now() == datetime(2012, 1, 14)
_

freeze_time()はコンテキストマネージャーとしても使用できます。モジュールは、ローカルタイムゾーンのUTCオフセットの指定をサポートしています。

23
jfs

これは、now()をオーバーライドするために使用するフィクスチャですが、残りの日時は機能し続けます(RE:satoruの質問)。

広範囲にテストされていませんが、datetimeが他のコンテキストで使用される問題を回避します。私にとってこれは、Django ORMがこれらの日時値(具体的にはisinstance(Freeze.now(), datetime.datetime) == True)で機能し続けるために重要でした。

@pytest.fixture
def freeze(monkeypatch):
    """ Now() manager patches datetime return a fixed, settable, value
        (freezes time)
    """
    import datetime
    original = datetime.datetime

    class FreezeMeta(type):
        def __instancecheck__(self, instance):
            if type(instance) == original or type(instance) == Freeze:
                return True

    class Freeze(datetime.datetime):
        __metaclass__ = FreezeMeta

        @classmethod
        def freeze(cls, val):
            cls.frozen = val

        @classmethod
        def now(cls):
            return cls.frozen

        @classmethod
        def delta(cls, timedelta=None, **kwargs):
            """ Moves time fwd/bwd by the delta"""
            from datetime import timedelta as td
            if not timedelta:
                timedelta = td(**kwargs)
            cls.frozen += timedelta

    monkeypatch.setattr(datetime, 'datetime', Freeze)
    Freeze.freeze(original.now())
    return Freeze

おそらく話題から外れていますが、この質問にたどり着く他の人々に役立つかもしれません。このフィクスチャは、「フリーズ」時間を可能にし、テスト内で自由に前後に移動します。

def test_timesensitive(freeze):
    freeze.freeze(2015, 1, 1)
    foo.prepare()  # Uses datetime.now() to prepare its state
    freeze.delta(days=2)
    # Does something that takes in consideration that 2 days have passed
    # i.e. datetime.now() returns a date 2 days in the future
    foo.do_something()
    assert foo.result == expected_result_after_2_days
3
Sebastian

他の回答から適応:

import datetime as dt

@contextmanager
def mocked_now(now):
    class MockedDatetime(dt.datetime):
        @classmethod
        def now(cls):
            return now

    with patch("datetime.datetime", MockedDatetime):
        yield

次のように使用されます:

def test_now():
    with mocked_now(dt.datetime(2017, 10, 21)):
        assert dt.datetime.now() == dt.datetime(2017, 10, 21)
2
Jake Levitt

MagicMock(wrap=datetime.datetime)を使ってみませんか?

このアプローチはdatetime.datetime.now()をモックしますが、他のメソッドは元のdatetime.datetimeと同じように使用できます。

from unittest.mock import MagicMock

def test_datetime_now(monkeypatch):
    import datetime
    FAKE_NOW = datetime.datetime(2020, 3, 11, 14, 0, 0)
    datetime_mock = MagicMock(wraps=datetime.datetime)
    datetime_mock.now.return_value = FAKE_NOW
    monkeypatch.setattr(datetime, "datetime", datetime_mock)

    assert datetime.datetime.now() == FAKE_NOW

    # the other methods are available
    assert datetime.datetime.fromisoformat("2020-03-01T00:00:00") == datetime.datetime(2020, 3, 1, 0, 0, 0)

@pytest.fixtureアプローチの使用はこちらです。

import datetime
from unittest.mock import MagicMock

import pytest

FAKE_NOW = datetime.datetime(2020, 3, 11, 14, 0, 0)


@pytest.fixture()
def mock_datetime_now(monkeypatch):
    datetime_mock = MagicMock(wraps=datetime.datetime)
    datetime_mock.now.return_value = FAKE_NOW
    monkeypatch.setattr(datetime, "datetime", datetime_mock)


def test_datetime_now2(mock_datetime_now):
    assert datetime.datetime.now() == FAKE_NOW

    assert datetime.datetime.fromisoformat("2020-03-01T00:00:00") == datetime.datetime(2020, 3, 1, 0, 0, 0)

0