Search code examples
python-unittestfreezegun

freeze_time not working for default param


I am having a function with the a default value of a param as datetime.now(). method is something like below,

def as_standard_format(p_date=datetime.now(), fmt=sdk_constants.DEFAULT_DATE_TIME_FORMAT):
    return p_date.strftime(fmt)

And I am having a test method something like below,

@freeze_time(datetime(year=2018, month=9, day=25, hour=15, minute=46, second=36))
def testNowWithDefaultFormat(self):

    # The below method gives the frozen time
    print(datetime.now())

    # But the below test fails
    assert utils.format_date(
        datetime.now()) == "20180925T154636Z", "Default call not giving current time in " \
                                               "standard format \"%Y%m%dT%H%M%SZ\""

Why is the freeze_time not working with default param value?


Solution

  • You're being victim of a common gotcha in Python related to mutable default value parameters and not specific only to freeze_time.

    For the explanation, consider the following code:

    import time from datetime import datetime
    
    def what_time_is_it(p_date=datetime.now()):
        return p_date.strftime('"%Y-%m-%d %X"')
    
    if __name__ == '__main__':
        print(what_time_is_it())
        time.sleep(10)
        print(what_time_is_it())
    

    What you might have expected to happen

    "2019-02-28 13:47:24"
    "2019-02-28 13:47:34" # This line showing a datetime 10 seconds after the first one
    

    A new datetime.now() is called on every call to what_time_is_it()

    What does happen

    "2019-02-28 13:47:24"
    "2019-02-28 13:47:24" # EXACTLY the same time in both lines!
    

    A new datetime.now() is created once when the function is defined, and the same value is used in each successive call.

    What you should do instead?

    Create a new object each time the function is called, by using a default arg to signal that no argument was provided (None is often a good choice):

    def as_standard_format(p_date=None, fmt=sdk_constants.DEFAULT_DATE_TIME_FORMAT):
        if p_date is None:
            return datetime.now().strftime(fmt)
        else:
            return p_date.strftime(fmt)