-
Notifications
You must be signed in to change notification settings - Fork 266
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
freeze_time doesn't freeze time of pytest fixtures #176
Comments
I can't think of any way to do this. Pytest seems to evaluate the function before running the test. Feel free to reopen if you can think of another way. |
The best way to do this reliably with py.test would be using markers. Something like this: https://docs.pytest.org/en/latest/example/markers.html#marking-platform-specific-tests-with-pytest |
markers would certainly be a good idea... question is though whether such should be part of freezegun directly or of a different pytest-freezegun plugin? |
Depends on who wants to maintain it. I guess a separate plugin would be nice as it would be more explicit (and it's a few instructions added to each test, think of performance! ;-)). Actually, I might whip something up later today - I've already written that code once, the core should be <50 lines. it's mostly packaging and testing. |
Well, that took longer than expected. I have a basic implementation here: https://github.com/ktosiek/pytest-freezetime/blob/master/pytest_freezegun.py. I'll put it on pypi when it's a bit more useful (mostly docs and access to the freezer in tests). |
@ktosiek |
@elcolie I don't think your issue has anything to do with pytest. Rather a django issue potentially. See https://docs.djangoproject.com/en/1.11/topics/i18n/timezones/. What I usually do to create a timezone aware datetime in Django
|
Github acts strange on my comment. Markup does not has a color on then. Neither
Here are my warning messges ===================================================================================== test session starts =====================================================================================
platform darwin -- Python 3.6.3, pytest-3.2.3, py-1.4.34, pluggy-0.4.0 -- /Users/sarit/.pyenv/versions/3.6.3/envs/poink/bin/python
cachedir: .cache
Django settings: poinkbackend.config.settings.local (from ini file)
rootdir: /Users/sarit/Code/poink, inifile: pytest.ini
plugins: django-3.1.2
collected 2 items
poinkbackend/apps/rewards/tests.py::test_queryset PASSED
poinkbackend/apps/rewards/tests.py::test_expired_reward PASSED
====================================================================================== warnings summary =======================================================================================
poinkbackend/apps/rewards/tests.py::test_expired_reward
/Users/sarit/.pyenv/versions/3.6.3/envs/poink/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1451: RuntimeWarning: DateTimeField Reward.active_date received a naive datetime (2017-11-01 09:30:23) while time zone support is active.
RuntimeWarning)
/Users/sarit/.pyenv/versions/3.6.3/envs/poink/lib/python3.6/site-packages/django/db/models/fields/__init__.py:1451: RuntimeWarning: DateTimeField Reward.expiry received a naive datetime (2017-11-21 09:30:23) while time zone support is active.
RuntimeWarning)
-- Docs: http://doc.pytest.org/en/latest/warnings.html
============================================================================ 2 passed, 2 warnings in 2.18 seconds =============================================================================
Please advise how to do it correct way. |
Not tested but I assume if you create the time you want to freeze with timezone.now() and .replace() as my example above you can pass it on to freeze_time and warning should disappear.
|
@sliverc I have to aware
@pytest.fixture
def rewards(branches):
import datetime
with freeze_time("2017-11-01 9:30:23", tz_offset=+7):
branch = Branch.objects.first()
mommy.make(Reward, title='LEGO Truck', poink=2, description='8-12yr', active_date=timezone.now(),
expiry=timezone.now() + datetime.timedelta(days=20), branch=branch)
mommy.make(Reward, title='LEGO SpaceShip', poink=10, description='8-12yr', active_date=timezone.now(),
expiry=timezone.now() + datetime.timedelta(days=20), branch=branch)
def test_expired_reward(rewards):
"""Must not be able to get redeem"""
with freeze_time("2099-11-01 9:30:23", tz_offset=+7):
assert 0 == Reward.objects.live_count() |
@ktosiek, is pytest-freezegun still supported? There is no activity since November. |
@utapyngo that doesn't sound very long to me. Why would a simple project like this warrant such a question? |
A colleague of mine does not want to use pytest-freezegun because it is not supported. All PRs seem to be ignored. |
There are just 4 PRs. In any case we use freezegun at work without using pytest-freezegun. It's extremely easy to set ut so no plug-in is needed imo. |
Yeah, I should take a look at those PRs. It works for me, in more than one project, if that's any consolation :-) As @boxed mentioned, using freezegun without the plugin is easy, as long as you only need the time frozen inside the test (and not fixtures). The plugin is handy if you want the whole fixture setup to use frozen time. Also, it's 50 lines of actual code - if you have a problem and I'm not responsive enough you can probably fix it (or even rewrite the whole thing). |
We actually freeze the time of all tests unless specifically marked as wanting real time. We use simple fixtures to do that. I've written about it here https://link.medium.com/Rz2FbQCUTZ |
As far as I can tell you can accomplish this by having a fixture which freezes the time:
|
Great tips @subwindow ! Adding a fixture works like a charm and it's descriptive.
|
I am using a similar approach to @GordonSo's most recent comment to test "interesting dates" so I can find bugs around e.g., Y2k. However, I find that when I pass pytest --durations=3 Then I see:
The relevant test code: from datetime import date, datetime, timedelta
from itertools import chain, product
from typing import Any, Optional
import pytest
from dateutil.parser import parse
from django.utils import timezone as tz
from freezegun import freeze_time
from core.utils import days_ago
_interesting_datetimes = (
tz.localtime(),
tz.make_aware(parse("Dec 31 1999, 23:59:59.9999")),
tz.make_aware(parse("Jan 01 2000, 00:00:00.0000")),
)
@pytest.fixture(params=_interesting_datetimes)
def freeze_shotgun(request):
with freeze_time(request.param):
yield request.param
@pytest.fixture(
params=chain(
(None,), _interesting_datetimes, (tz.localdate(dt) for dt in _interesting_datetimes)
)
)
def now(request):
return request.param
def is_date(value):
return isinstance(value, date)
def is_datetime(value: Any, tz_aware: Optional[bool] = None):
if not isinstance(value, datetime):
return False
elif tz_aware is None:
return True
else:
return tz.is_aware(value) == tz_aware
@pytest.mark.parametrize(
"days,dt",
product(
{0, 1, -1},
{True, False},
),
)
def test_days_ago(days, now, dt, freeze_shotgun):
result = days_ago(days, now=now, dt=dt)
assert is_datetime(result, tz_aware=True) if dt else is_date(result)
now_date = tz.localdate(now) if is_datetime(now) else tz.localdate() if now is None else now
result_date = tz.localdate(result) if is_datetime(result) else result
assert is_date(result_date) and is_date(now_date)
assert (now_date - result_date) == timedelta(days=days) And the relevant implementation code: from datetime import datetime, date
from typing import Union
from django.utils import timezone
def days_ago(days: int, now: Union[datetime, date, None] = None, dt=False) -> Union[datetime, date]:
"""
Returns what date (or datetime) it was `days` from `now`.
`days`: a positive integer for past dates,
or a negative integer for future dates.
`now`: the date to subtract `days` from. If None is given, the
current date is used
`dt`: return a datetime rather than a date. The time component will be
equal to the current time of day, unless `now` is given and includes
a time component (in which case, the given time component is preserved.)
"""
if now is None:
if dt:
now = timezone.localtime()
else:
now = timezone.localdate()
else:
if dt and isinstance(now, date):
now = timezone.make_aware(datetime.combine(now, timezone.localtime().time()))
if not dt and isinstance(now, datetime):
now = timezone.localtime(now).date()
return now - timezone.timedelta(days=days) @spulec do you have any thoughts on how I might "untrick" pytest? (Should I open a separate issue for this?) |
Immediately after posting the above comment, I found pytest-durations which more accurately reports the time and is suitable for my use case. Thanks! |
I use this pattern which works great: @pytest.fixture
@freeze_time("2022-09-01 12:00:00")
def now():
return datetime.now()
@pytest.fixture
def yesterday(now):
return now - timedelta(days=1)
@pytest.fixture
def tomorrow(now):
return now + timedelta(days=1)
def test_time(yesterday, tomorrow):
print(yesterday)
print(tomorrow)
assert False --------------------------- Captured stdout call ----------------------------
2022-08-31 12:00:00
2022-09-02 12:00:00 Note that the order of the decorators matter. |
Following test fails which shows that freeze_time only freezes the time of the test but not of dependent fixtures.
Is there a way to freeze time for depending fixtures as well?
The text was updated successfully, but these errors were encountered: