7

I have the follow class and method:

class DateTimeHelper(object):

    @staticmethod
    def get_utc_millisecond_timestamp():
        (dt, micro) = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f').split('.')
        return "%s.%03d" % (dt, int(micro) / 1000)  # UTC time with millisecond

How can I unit test it? I am completely stumped although this is simple. It's my first unit test.

jonrsharpe
  • 115,751
  • 26
  • 228
  • 437
DIzzyDiza
  • 81
  • 1
  • 6

1 Answers1

10

Use the unittest.mock library (Python 3.3 and newer, backported as mock), to replace calls to any code external to your code-under-test.

Here, I'd mock out not only utcnow() but strftime() too, to just return a string object:

with mock.patch('datetime.datetime') as dt_mock:
    dt_mock.utcnow.return_value.strftime.return_value = '2016-08-04 12:22:44.123456'
    result = DateTimeHelper.get_utc_millisecond_timestamp()

If you feel that testing the strftime() argument is important, give dt_mock.utcnow.return_value an explicit datetime object to return instead; you'd have to create that test object before you mock however, as you can't mock out just the datetime.datetime.utcnow class method:

testdt = datetime.datetime(2016, 8, 4, 12, 22, 44, 123456)
with mock.patch('datetime.datetime') as dt_mock:
    dt_mock.utcnow.return_value = testdt
    result = DateTimeHelper.get_utc_millisecond_timestamp()

or, in your unittests, use from datetime import datetime to keep a reference to the class that isn't mocked.

Demo:

>>> from unittest import mock
>>> import datetime
>>> class DateTimeHelper(object):
...     @staticmethod
...     def get_utc_millisecond_timestamp():
...         (dt, micro) = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S.%f').split('.')
...         return "%s.%03d" % (dt, int(micro) / 1000)  # UTC time with millisecond
...
>>> with mock.patch('datetime.datetime') as dt_mock:
...     dt_mock.utcnow.return_value.strftime.return_value = '2016-08-04 12:22:44.123456'
...     result = DateTimeHelper.get_utc_millisecond_timestamp()
...
>>> result
'2016-08-04 12:22:44.123'
>>> testdt = datetime.datetime(2016, 8, 4, 12, 22, 44, 123456)
>>> with mock.patch('datetime.datetime') as dt_mock:
...     dt_mock.utcnow.return_value = testdt
...     result = DateTimeHelper.get_utc_millisecond_timestamp()
...
>>> result
'2016-08-04 12:22:44.123'
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 1
    The [freezegun](https://github.com/spulec/freezegun) 3rd party module can also be quite useful and a time saver in some cases... (has handy decorators and context managers) – Jon Clements Aug 31 '16 at 14:47
  • `freezegun` produces elaborate mocks; I've so far not seen any use case where you couldn't achieve the same with `mock`. With `mock` being the defacto standard I'm not sure I'd ever choose `freezegun` instead. – Martijn Pieters Aug 31 '16 at 14:51
  • I've not seen where I couldn't reproduce anything using `urllib` that I couldn't with `requests`... it's mostly convenience - that's all. – Jon Clements Aug 31 '16 at 14:55
  • @NinjaPuppy: the difference is much, much bigger between `urllib` and `requests` though. And `mock` lets me test, in detail, how my mocks were called, something `freezegun` doesn't let me do. – Martijn Pieters Aug 31 '16 at 14:56
  • I'm not saying that one or t'other is best. For when I've used it for mocking, it's easier to classify an entire suite using known parameters than using context managers etc... like I said - it's just convenience... – Jon Clements Aug 31 '16 at 15:08
  • This has got to be wrong, because it does not work whatsoever, what are you asserting? How is this a test? – DIzzyDiza Sep 07 '16 at 18:44
  • @DIzzyDiza: I gave you the options to mock the `datetime` calls; you can now control the input and test the output against expected strings. – Martijn Pieters Sep 07 '16 at 19:25