Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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
        ....
like image 275
Dhia Avatar asked Feb 01 '17 13:02

Dhia


2 Answers

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.

like image 145
Linovia Avatar answered Nov 04 '22 11:11

Linovia


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 …
like image 1
Matt Deacalion Avatar answered Nov 04 '22 09:11

Matt Deacalion