Search code examples
pythondatetimemockingpytest

pytest - fake time changes during test


I have the following code to set a faketime during tests.

I'ld like to change the time during a test. That is, the test should start at 9:00 for instance and then continue as if it is 10:00 .

from __future__ import annotations

import datetime
import logging

import pytest

LOGGER = logging.getLogger(__name__)


@pytest.fixture(params=[datetime.datetime(2020, 12, 25, 17, 5, 55)])
def patch_datetime_now(request, monkeypatch):
    class mydatetime(datetime.datetime):
        @classmethod
        def now(cls):
            return request.param

    class mydate(datetime.date):
        @classmethod
        def today(cls):
            return request.param.date()

    monkeypatch.setattr(datetime, "datetime", mydatetime)
    monkeypatch.setattr(datetime, "date", mydate)


@pytest.mark.usefixtures("patch_datetime_now")
@pytest.mark.parametrize(
    "patch_datetime_now", [(datetime.datetime(2020, 12, 9, 11, 22, 00))], indirect=True
)
def test_update_data():
    fakeTime = datetime.datetime.now()
    # Do some stuff

    # Change the fake time

    # Do some other stuff

How can I change the fake time during the test. The 'datetime' is used inside the code tested, so it's not about changing the "fakeTime" variable contents, but about changing the time returned by the datetime mockup.

Maybe I need to change the mocking method completely, I am just sharing my current code.


Solution

  • Following this answer on another question provided by @MrBeanBremen I updated my code like this:

    from __future__ import annotations
    
    import datetime
    import logging
    
    import pytest
    
    LOGGER = logging.getLogger(__name__)
    
    
    @pytest.fixture(params=[datetime.datetime(2020, 12, 25, 17, 5, 55)])
    def patch_datetime_now(request, monkeypatch):
        def _delta(timedelta=None, **kwargs):
            """ Moves time fwd/bwd by the delta"""
            from datetime import timedelta as td
            if not timedelta:
                timedelta = td(**kwargs)
            request.param += timedelta
    
        class mydatetime(datetime.datetime):
            @classmethod
            def now(cls):
                return request.param
    
            @classmethod
            def delta(cls,*args,**kwargs):
                _delta(*args,**kwargs)
    
        class mydate(datetime.date):
            @classmethod
            def today(cls):
                return request.param.date()
    
            @classmethod
            def delta(cls,*args,**kwargs):
                _delta(*args,**kwargs)
    
        monkeypatch.setattr(datetime, "datetime", mydatetime)
        monkeypatch.setattr(datetime, "date", mydate)
    
    
    # Test & Example using the fixture above
    @pytest.mark.usefixtures("patch_datetime_now")
    @pytest.mark.parametrize(
        "patch_datetime_now", [(datetime.datetime(2020, 12, 9, 11, 22, 00))], indirect=True
    )
    def test_update_data():
        # The test starts with the fake time set in the parameter above
        fakeTime = datetime.datetime.now()
        # Assert (verify) that the fake time is used
        assert fakeTime == datetime.datetime(2020, 12, 9, 11, 22, 00)
        # Move the fake time 1 hour and 10 seconds in the future
        datetime.datetime.delta(hours=1,seconds=10)
        # Get the new fake time
        fakeTime = datetime.datetime.now()
        # Assert (verify) that the fake time was updated as expected
        assert fakeTime == datetime.datetime(2020, 12, 9, 12, 22, 10)