adarsh
adarsh

Reputation: 6978

How to mock/set system date in pytest?

In some of my tests I am having a problem that they fail on Travis because of time and time zone problems, so I want to mock system time for my test. How can I do this?

Upvotes: 35

Views: 38320

Answers (7)

Aneel Azzam
Aneel Azzam

Reputation: 71

You can mock built-in modules used within a specific module in your application.

For example, consider this module:

code.py

from datetime import datetime

current_timestamp = datetime.now()

To mock the datetime module in your tests, you can do the following:

test_code.py

import pytest
from datetime import datetime, timedelta

def test_code(mocker):
    mock_datetime = mocker.patch("{path_to_code_file}.code.datetime")
    mock_datetime.now.return_value = datetime.now() + timedelta(days=1)

Here, {path_to_code_file} should be replaced with the actual import path of the code.py file.

Upvotes: 3

ndc85430
ndc85430

Reputation: 1783

Dependency injection is another way to do it - just passing in something that gives you the time (e.g. a function).

Here are some example tests:


import datetime

from greeter import greet

def test_it_returns_good_morning_before_12():
    def get_time():
        return datetime.datetime(2024, 12, 8, hour=5, minute=0, second=0)

    greeting = greet(get_time)

    assert greeting == "Good morning!"

def test_it_returns_good_afternoon_after_12():
    def get_time():
        return datetime.datetime(2024, 12, 8, hour=14, minute=0, second=0)

    greeting = greet(get_time)

    assert greeting == "Good afternoon!"

The greet function then calls the function it is passed to obtain the time:

def greet(get_time):
    hour = get_time().hour

    if hour < 12:
        return "Good morning!"
    else:
        return "Good afternoon!"

In the production code where I need to use greet, I'd simply pass the function that provides the system time:

greeting = greet(datetime.datetime.now)

I did write a tutorial about this over on python-forum: https://python-forum.io/thread-38222.html. That does include a link to a repo on GitHub with a more complete example (https://github.com/ndc85430/controlling-date-and-time-in-tests).

Upvotes: 0

Nerxis
Nerxis

Reputation: 3917

Similarly to freezgun (see @Jason R. Coombs answer) you can use time-machine which should be faster.

import datetime as dt
import time_machine

@time_machine.travel("2024-01-01")
def test_date():
    assert dt.datetime.now() ==  dt.datetime(2024, 1, 1)

For more examples check time-machine.

Upvotes: 0

Ehsan
Ehsan

Reputation: 4291

I prefer to use this code.

from unittest.mock import MagicMock


@pytest.fixture
def mocking_datetime_now(monkeypatch):
    datetime_mock = MagicMock(wrap=datetime.datetime)
    datetime_mock.now.return_value = datetime.datetime(2020, 3, 11, 0, 0, 0)

    monkeypatch.setattr(datetime, "datetime", datetime_mock)


@pytest.fixture
def setup_db(company, company_user, mocking_datetime_now):
    assert datetime.datetime.now() == datetime.datetime(2020, 3, 11, 0, 0, 0)

Upvotes: 4

Jason R. Coombs
Jason R. Coombs

Reputation: 42679

@Brian-Kruger's answer is the best one. I've voted to undelete it. In the meantime...

Use freezegun (repo).

From the README:

from freezegun import freeze_time

@freeze_time("2012-01-14")
def test():
    assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)

Upvotes: 48

Bruno Oliveira
Bruno Oliveira

Reputation: 15255

AFAIK, you can't mock builtin methods.

One approach I have often done is to change my code a bit to not use datetime directly to obtain the date, but a wrapper function somewhere:

# mymodule.py

def get_today():
   return datetime.date.today()

This makes it trivial to just mock it in your test:

def test_something():
    with mock.patch('mymodule.get_today', return_value=datetime.date(2014, 6, 2)):
        ...

You can also use the freezegun module.

Upvotes: 29

sashk
sashk

Reputation: 4213

There are two ways you can accomplish that:

  1. Create function which you will call instead of datetime.datetime.now() as suggested by Bruno, but here is different implementation:

    import os
    import datetime
    
    def mytoday():
     if 'MYDATE' in os.environ:
         return datetime.datetime.strptime(os.getenv('MYDATE'), '%m-%d-%Y').date()
     else:
         return datetime.date.today()
    

    Then, in your test, you just monkeypatch environment variable:

    import datetime
    
    def test_patched_date(monkeypatch):
        monkeytest.setenv('MYDATE', '05-31-2014')
        assert datetime.date.today() == datetime.date(2014, 5, 31)
    
  2. Monkeypatch the datetime function:

    import datetime
    import pytest
    
    FAKE_TIME = datetime.datetime(2020, 12, 25, 17, 05, 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
    

Upvotes: 19

Related Questions