Mocking time issue in django test: time seems not to be frozen using freezegun
Asked Answered
R

2

7

I'm writing here a functional test to check if my API throttling is working as expected (will be rest at the beginning of every month).

Testing Class:

class ApiThrottlingTest(ThrottlingBaseTest):

    def test_throttling_purchaser_case(self):

        now = datetime.datetime(year=2015, month=1, day=10, hour=6, minute=6, second=3)

        last_day_of_current_month = datetime.datetime(year=2015, month=1, day=31, hour=23, minute=59, second=59)

        first_day_of_next_month = datetime.datetime(year=2015, month=2, day=1, hour=0, minute=0, second=0)

        with freeze_time(now) as frozen_datetime:
            for i in xrange(3):
                resp = self._project_details_request()
                self.assertEqual(resp.status_code, 200)

            resp = self._project_details_request()
            self.assertEqual(resp.status_code, 429)

            frozen_datetime.move_to(last_day_of_current_month)
            resp = self._project_details_request()
            # the test fails at this level
            self.assertEqual(resp.status_code, 429)

            frozen_datetime.move_to(first_day_of_next_month)
            resp = self._project_details_request()
            self.assertEqual(resp.status_code, 200)

The test works fine if: last_day_of_current_month = datetime.datetime(... second=0)
but will fail if: last_day_of_current_month = datetime.datetime(... second=59)

After debugging it seems like the time module used in DjangoRestFramework throttling.UserRateThrottle is in somehow giving a value that's always ahead of the fronzen time in my test, which is causing a precision issue of some seconds.

Based on FreezeGun Doc time.time() should be also frozen:

Once the decorator or context manager have been invoked, all calls to datetime.datetime.now(), datetime.datetime.utcnow(), datetime.date.today(), time.time(), time.localtime(), time.gmtime(), and time.strftime() will return the time that has been frozen.

But it looks like im my case time.time takes correctly the start time of the mocked datetime but then keep changing over time which is not expected, it's expected to be frozen till the time is manually forwarded.

I tried to mock time.time used in UserRateThrottle seperatly using mock module but still did not solve the issue.

----> Any idea what could be the issue, and how could be possibly resolved?

Test Fail: (after time is forwarded to the last day of the month: line 14)

self.assertEqual(resp.status_code, 429)
AssertionError: 200 != 429

The DRF class source code:

class SimpleRateThrottle(BaseThrottle):
    ...

    cache = default_cache
    timer = time.time 
    cache_format = 'throttle_%(scope)s_%(ident)s'

    def __init__(self):
       ....

    def allow_request(self, request, view):
        ...

        self.now = self.timer() # here timer() returns unexpected value in test
        ....
Rozanne answered 1/2, 2017 at 13:39 Comment(0)
P
2

you'll need to override the SimpleRateThrottle's timer with the FreezeGun's time.time.

What's happening here is that feezegun likely override the Python's time module. However, SimpleRateThrottle doesn't use the module, it uses the module's function which becomes out of reach of freeze gun.

SimpleRateThrottle therefore uses the Python standard library time module while other part of the code use the freezegun's one.

Edit: You should do - after FreezeGun has been activated:

former_timer = SimpleRateThrottle.timer
SimpleRateThrottle.timer = time.time

and once your test is over (in the tearDown or whatever equivalent):

SimpleRateThrottle.timer = former_timer

Note that you could also use a monkey patching lib to handle that for you.

Priebe answered 1/2, 2017 at 13:53 Comment(4)
How can I override it with FreezeGun!? In fact I thought about it and tried to mock the returned value of module'function used by SimpleRateThrottle manually using monkey patching using the python mock module but still have the same problem!!!Rozanne
sorry, realized that after I wrote my answer. It's now edited with an how to.Priebe
Thanks, but still have the same issue.Rozanne
Might be if you/drf instantiated the SimpleRateThrottlebefore hitting the testPriebe
S
1

If you're using pytest, you could do this:

import pytest
from time import time
from rest_framework.throttling import SimpleRateThrottle


@pytest.mark.freeze_time('2020-09-23 00:00:00')
def test_something(monkeypatch):
    monkeypatch.setattr(SimpleRateThrottle, 'timer', lambda _: time())

    # … unit test here …
Sentience answered 23/9, 2020 at 12:45 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.