For the sake of simplicity, we will operate as if we are building a brand new repo:
Let's create the hello-python-datetime directory and install the required dependencies.
# Make the `hello-python-datetimes` directory
$ mkdir hello-python-datetimes
$ cd hello-python-datetimes
# Init the virtual environment
$ pipenv --three
$ pipenv install --dev ipython freezegun types-freezegun
# Create a folder to place files
$ mkdir src tests
# Create the required files
$ touch src/datetimes.py src/__init__.py tests/datetimes_test.py tests/__init__.py main.py
At this stage, we are now ready to update our main.py file and src/datetimes.py to be up to par with what we need for testing.
Add the following to src/datetimes.py:
from datetime import date
def is_date_before_today(date_str: str):
"""Check if date is before today
Args:
date_str (str): String of a date to pass
Returns:
bool: Value of if date is before today
"""
try:
date_obj = date.fromisoformat(date_str)
return date_obj < date.today()
except Exception:
return False
Add the following to main.py:
from src.datetimes import is_date_before_today
from datetime import datetime, timedelta
print(is_date_before_today("2019-01-01"))
print(is_date_before_today("2022-01-01"))
print(is_date_before_today("2021-08-03"))
print(is_date_before_today("2021-08-04"))
now = datetime.now()
now_str = now.strftime('%Y-%m-%d')
print(now_str)
print(is_date_before_today(now_str))
now_subtract_one_day = now - timedelta(days=2)
now_subtract_one_day_str = now_subtract_one_day.strftime('%Y-%m-%d')
print(now_subtract_one_day_str)
print(is_date_before_today(now_subtract_one_day_str))
now_add_one_day = now + timedelta(days=1)
now_add_one_day_str = now_add_one_day.strftime('%Y-%m-%d')
print(now_add_one_day_str)
print(is_date_before_today(now_add_one_day_str))
Running python main.py should bring us up to par with the following:
The output matches up to us printing values out from main.py. We are at a stage now where we are up to par and able to start writing tests.
Note: If you are using a virtual environment, you will need to run pipenv shell to enter the virtual environment.
Exploring FreezeGun with PyTest
FreezeGun is a library that helps with mocking out the datetime.datetime.now function. It is a very useful tool for testing code that uses the datetime library.
We can use the library with a decorator for the test or creating a with block.
To demonstrate, add the following code to tests/datetimes_test.py:
import datetime
from freezegun import freeze_time
from src.datetimes import is_date_before_today
def test_freeze_time():
assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)
# Mocking the time to be 2012-01-14
with freeze_time("2012-01-14"):
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
# Without the mock, the time should be back to normal
assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)
@freeze_time("2012-01-14")
def test_freeze_time_with_decorator():
# Testing with a decorator that mocks throughout the test
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
The first test demonstrates the with block will the second test demonstrates usage with a decorator.
Running pipenv run pytest will now run the tests and display the results.
$ pipenv run pytest
pipenv run pytest
================================== test session starts ===================================
platform darwin -- Python 3.9.6, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /path/to/hello-python-datetimes
collected 2 items
tests/test_datetimes.py .. [100%]
=================================== 2 passed in 0.21s ====================================
Now we are ready to test our is_date_before_today function in a manner similar to how our main.py invokes the functions.
Testing the is_date_before_today function
Let's update our tests/datetimes_test.py file to test the is_date_before_today function.
import datetime
from freezegun import freeze_time
from src.datetimes import is_date_before_today
def test_freeze_time():
assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)
# Mocking the time to be 2012-01-14
with freeze_time("2012-01-14"):
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
# Without the mock, the time should be back to normal
assert datetime.datetime.now() != datetime.datetime(2012, 1, 14)
@freeze_time("2012-01-14")
def test_freeze_time_with_decorator():
# Testing with a decorator that mocks throughout the test
assert datetime.datetime.now() == datetime.datetime(2012, 1, 14)
# Converting the output we expected from main.py into a set of tests.
# Mocking time unnecessary, but done for the sake of completion.
@freeze_time("2021-08-05")
def test_is_date_before_today():
"""Should return False"""
now = datetime.datetime.now()
now_str = now.strftime('%Y-%m-%d')
assert is_date_before_today(now_str) is False
@freeze_time("2021-08-05")
def test_is_one_day_ago_before_today():
"""Should return True"""
now_subtract_one_day = datetime.datetime.now() - datetime.timedelta(days=1)
now_subtract_one_day_str = now_subtract_one_day.strftime('%Y-%m-%d')
assert is_date_before_today(now_subtract_one_day_str) is True
@freeze_time("2021-08-05")
def test_is_one_day_ahead_before_today():
"""Should return False"""
now_add_one_day = datetime.datetime.now() + datetime.timedelta(days=1)
now_add_one_day_str = now_add_one_day.strftime('%Y-%m-%d')
assert is_date_before_today(now_add_one_day_str) is False
In our test, we are freezing time (using the decorator) to the date of this blog post 2021-08-05 checking the following scenarios:
is_date_before_today when compared to today should be False.
is_date_before_today when compared to one day ago should be True.
is_date_before_today when compared to one day ahead should be False.
We can confirm this to be true by once again running pipenv run pytest:
$ pipenv run pytest
================================== test session starts ===================================
platform darwin -- Python 3.9.6, pytest-6.2.4, py-1.10.0, pluggy-0.13.1
rootdir: /Users/dennisokeeffe/code/blog-projects/hello-python-datetimes
collected 5 items
tests/test_datetimes.py ..... [100%]
=================================== 5 passed in 0.21s ====================================
Summary
Today's post demonstrated how to use the FreezeGun package to mock the date for testing with PyTest.