28

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?

adarsh
  • 6,738
  • 4
  • 30
  • 52
  • 2
    What time function do you want to mock? If it's the Python datetime, it's a little tricky as explained [here](http://stackoverflow.com/questions/4481954/python-trying-to-mock-datetime-date-today-but-not-working) – Seb D. Jun 02 '14 at 08:00
  • 1
    I want to do this for all modules. I think the freezegun example works in the link you gave. Thanks! – adarsh Jun 02 '14 at 08:11

4 Answers4

39

@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)
Jason R. Coombs
  • 41,115
  • 10
  • 83
  • 93
28

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.

Bruno Oliveira
  • 13,694
  • 5
  • 43
  • 41
  • `setattr(datetime.datetime, 'today', get_today)` ? – MaxNoe Dec 16 '16 at 10:43
  • As mentioned, this gives `TypeError: can't set attributes of built-in/extension type 'datetime.datetime'` on Python 2 and 3. – Bruno Oliveira Dec 22 '16 at 02:17
  • 1
    Ah, yes. You have to replace the complete class `datetime`. You can inherit from datetime and override the methods you need. But `freezetime` works perfectly. – MaxNoe Dec 22 '16 at 07:54
  • I think that the idea of a wrapper function is the best answer given across every thread I've found because it is agnostic to unit testing libraries so it will always work. – Andrew Samuelson Aug 22 '21 at 21:14
18

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
    
sashk
  • 3,946
  • 2
  • 33
  • 41
  • is the indentation correct or something missing? after "def patch_datetime_now(monkeypatch): " there is no indented block. maybe at least add a comment in this code line, it confuses some people like me – SHernandez Jun 09 '15 at 12:08
  • For the second suggestion, if the module under test loads the datetime before patching (e.g. `from datetime import datetime`), it will hold an instance to the old datetime and not `mydatetime`. – Tim Diels Dec 14 '15 at 21:56
  • 2
    I now realise I was referring to [this gotcha](http://www.voidspace.org.uk/python/mock/patch.html#where-to-patch) of patching. So, if the module under test (mut.py) imports datetime before you get to patch `datetime.datetime`, patch `mut.datetime` instead. – Tim Diels Dec 14 '15 at 22:42
3

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)
Ehsan
  • 3,711
  • 27
  • 30
  • Trying to use this, but from `conftest.py`. Yeah, I add it to the test parameters `def test_foo(mocking_datetime_now)`, but then what? – tim.rohrer Feb 05 '23 at 01:13
  • For my situation, I'm realizing now for my situation, mocking the entire class is problematic since I need to use `strptime` elsewhere. – tim.rohrer Feb 05 '23 at 01:45