While writing automated tests for Django you soon or later will need to control the internal clock of the system. There are several obvious ways to do that and I will try to point out the pros and cons of each one.
Although I’m saying Django most of those ideas can be applied to any python application.
Receive the datetime object as an optional argument
This is the simplest way. The idea here is to pass a function as argument that returns the current datetime, e.g.
is_expired implementation must get the datetime calling the function
now. Defaults to
class Promotion(models.Model): def is_expired(self, now=timezone.now): system_datetime = now() ...
def test_is_expired(self): now = lambda: datetime(2020, 10, 10) assert self.promotion.is_expired(now=now) == True
- Dirty and confusing interfaces.
Mock the standard datetime library
Using the mock library to do that, e.g.
@patch('yourpackage.datetime') def test_is_expired(self, datetime_mock): datetime_mock.now.return_value = datetime(2020, 10, 10) assert self.promotion.is_expired() == True
- Fits very well with the python development philosophy;
- Moderate complexity.
- Troublesome if the datetime needs to be mocked system-wide, e.g. in the development server.
We can call this the most object oriented solution to the problem. Create a service interface that provides a method to retrieve the time, and then mock it as needed. e.g.
class TimeService(object): def now(self): return timezone.now()
is_expired implementation must get the datetime through
class PromotionService(object): def __init__(self, timeservice): self._timeservice = timeservice def is_expired(promotion): system_datetime = self._timeservice.now() ...
The example is using the
create_autospecfunction from python standard library.
def test_is_expired(self): timeservice = create_autospec(TimeService) timeservice.now.return_value = datetime(2020, 10, 10) service = PromotionService(timeservice) assert service.is_expired(self.promotion) == True
- High cohesion, low coupling;
- Clear interfaces, dependencies are explicitly set in the constructor;
- Easy to replace the
TimeServicewith a fake one and apply it system-wide.
- Complex and verbose;
- Doesn’t fits well with the python development philosophy.
Neither of the solutions is perfect and thus people generally choose between them based on their background and programming style. Do you know another solution? Share with us in the comments!