0

I have a class containing a timestamp. In the init function the initial timestamp is set to the argument provided, with a default value of 'time.monotonic()'. I also have and update function that sets the timestamp, also using time.monotonic()

When unit-testing this class I want to mock out time.monotonic, to get a predictable result.

However, the call in the default argument is always to the real time.monotonic

ClassWithTimestamp.py:

import time

class ClassWithTimestamp:
    def __init__(self, value={}, timestamp=time.monotonic()):
        self.timestamp = timestamp

    def update(self):
        self.timestamp = time.monotonic()

ClassWithTimestampTest.py:

import unittest
from unittest import mock

import ClassWithTimestamp

class ClassWithTimestampTest(unittest.TestCase):
    def setUp(self):
        ClassWithTimestamp.time.monotonic = mock.Mock(name='now')
        ClassWithTimestamp.time.monotonic.return_value = 1000

    def tearDown(self):
        pass

    def test_init(self):
        sut = ClassWithTimestamp.ClassWithTimestamp()
        self.assertEqual(sut.timestamp, 1000)

    def test_update(self):
        sut = ClassWithTimestamp.ClassWithTimestamp()
        sut.update()
        self.assertEqual(sut.timestamp, 1000)

if __name__ == '__main__':
    unittest.main()

When run:

python3 ClassWithTimestampTest.py -v
test_init (__main__.ClassWithTimestampTest) ... FAIL
test_update (__main__.ClassWithTimestampTest) ... ok

======================================================================
FAIL: test_init (__main__.ClassWithTimestampTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "ClassWithTimestampTest.py", line 16, in test_init
    self.assertEqual(sut.timestamp, 1000)
AssertionError: 762811.874163785 != 1000

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)
Pelle
  • 1,222
  • 13
  • 18

1 Answers1

0

I found this:

"Least Astonishment" and the Mutable Default Argument

It explains my problem, the default argument is called at the time of defining the init function, not executing it.

So my code was buggy to begin with as I intended the timestamp to be set to the construction time.

My revised code is

def __init__(self, value={}, timestamp=None):
    if timestamp:
        self.timestamp = timestamp
    else:
        self.timestamp = time.monotonic()

The problem itself still stands, the only way I see is to overwrite the member of the class after construction with the output of the mocked function.

Community
  • 1
  • 1
Pelle
  • 1,222
  • 13
  • 18