I'm trying to get my head around Mock and patch(), but I'm stuck on a somewhat simple example. Say I have the following function in main.py, which tests if it's a weekday or not.
from datetime import datetime
def is_weekday():
today = datetime.today()
return (0 <= today.weekday() < 5)
I want to run my test with two possible outcomes: either False if I mock Saturday or Sunday or True if it's a weekday. Now, I'm clearly not mocking anyting when calling main.is_weekday so my test currently fails as it's the weekend. How can I fix that?
from unittest.mock import Mock, patch
import pytest
import main
def test_weekday():
datetime = Mock()
tuesday = datetime(year=2019, month=1, day=1)
datetime.today.return_value = tuesday
with patch.object(datetime, "main.is_weekday", tuesday) as mock_method:
expected = True
actual = main.is_weekday() # how do I mock datetime.today()?
assert actual == expected
The essential problem is that you're not patching datetime in your main module. The first argument to patch.object is the thing you want to patch, and since you're passing in your datetime Mock object, that doesn't do you any good.
I would restructure your test like this:
from unittest.mock import Mock, patch
from datetime import datetime
from mockito import when, ANY
import main
def test_weekday():
testdate = datetime(year=2019, month=1, day=1)
with patch("main.datetime", wraps=datetime) as mock_datetime:
mock_datetime.today.return_value = testdate
assert main.is_weekday()
def test_weekend():
testdate = datetime(year=2019, month=1, day=5)
with patch("main.datetime", wraps=datetime) as mock_datetime:
mock_datetime.today.return_value = testdate
assert not main.is_weekday()
Here, we're replacing main.datetime with a mock object, and then configuring it such that calling datetime.today() in the main module will return a specific date.
Then we test that the code works as expected for both weekdays and
weekends.
Related
I found a strange behavior of mocking. Could you please explain me where is the pitfall?
Searching on the net for examples for mocking i found the above piece of code, which works.
But if i rewrite it as unittest.TestCase subclass, the mocking does not works.
import datetime
from unittest.mock import Mock
tuesday = datetime.datetime(year=2019, month=1, day=1)
saturday = datetime.datetime(year=2019, month=1, day=5)
def is_weekday():
today = datetime.datetime.today()
print( "Today is %d/%d/%d"%(today.day, today.month, today.year))
return (0 <= today.weekday() < 5)
datetime = Mock()
datetime.datetime.today.return_value = tuesday
assert is_weekday()
datetime.datetime.today.return_value = saturday
assert not is_weekday()
The above code will produce as expected the following
Today is 1/1/2019
Today is 5/1/2019
I rewrote the above code as subclass of unittest.TestCase
import unittest
import datetime
from unittest.mock import Mock
tuesday = datetime.datetime(year=2019, month=1, day=1)
saturday = datetime.datetime(year=2019, month=1, day=5)
def is_weekday():
today = datetime.datetime.today()
return (0 <= today.weekday() < 5)
class MyTestCase(unittest.TestCase):
def test_something(self):
datetime = Mock()
datetime.datetime.today.return_value = tuesday
assert is_weekday()
datetime.datetime.today.return_value = saturday
assert not is_weekday()
if __name__ == '__main__':
unittest.main()
But now the output is different
Today is 16/7/2021
Today is 16/7/2021
Python is lexically scoped; is_weekday uses a global name datetime, not the local name datetime you assigned the Mock to.
class MyTestCase(unittest.TestCase):
def test_something(self):
global datetime
old_datetime = datetime
datetime = Mock()
datetime.datetime.today.return_value = tuesday
assert is_weekday()
datetime.datetime.today.return_value = saturday
assert not is_weekday()
datetime = old_datetime
Better, though, would be to use unittest.mock.patch
from unittest.mock import patch
class MyTestCase(unittest.TestCase):
#patch('datetime.datetime')
def test_something(self, mock_dt):
mock_dt.today.return_value = tuesday
assert is_weekday()
mock_dt.today.return_value = saturday
assert not is_weekday()
I have the following test code to test Decorator.
#mock.patch('a.b.c.KafkaProducer')
def test_1(self, mocked):
decorator = Decorator(....)
#decorator()
def test():
return 42
test()
start_time = ???
v = {'type': 'batch', 'start_time': start_time.isoformat()}
mocked.return_value.send.assert_called_once_with(value=v)
However, the test always fail because Decorator calls mocked with dictionary parameter with property of start_time assigned to datetime.now(). Is it a way to compare everything except start_time? Or any other way to test the call?
Two practical approaches:
freeze time using https://pypi.org/project/freezegun/
import datetime
from freezegun import freeze_time
from unittest.mock import patch
import my_library
NOT_NOW = datetime.datetime.now()
#freeze_time("2020-01-01")
#patch("my_library.helper")
def test_foo(_helper):
my_library.under_test(NOT_NOW)
# kinda auto-magic
_helper.assert_called_once_with(datetime.datetime.now())
# or more explicitly
_helper.assert_called_once_with(datetime.datetime(2020, 1, 1))
or, evaluate arguments manually
#patch("my_library.helper", return_value=42)
def test_bar(_helper):
my_library.under_test(NOT_NOW)
assert _helper.call_count == 1
assert _helper.call_args[0][0]
assert _helper.call_args[0][0] != NOT_NOW
As the title says. I want to mock an imported function from a module. In this case the module is datetime and I want to mock datetime.datetime.now. I included what I've done without success.
# main.py:
import datetime
# cannot modify
def call_me(func):
return func()
class A:
variable = call_me(datetime.datetime.now)
# conftest.py:
import pytest
import datetime
FAKE_TIME = datetime.datetime.fromisoformat("2020-03-19T03:30:00")
#pytest.fixture(autouse=True)
def patch_datetime_now(monkeypatch):
class mydatetime(datetime.datetime):
#classmethod
def now(cls):
return FAKE_TIME
monkeypatch.setattr('datetime.datetime', mydatetime)
# test_main.py:
import datetime
from main import A
def test_main():
assert A.variable == datetime.datetime.now()
❯ pytest
------------------------------------------------------------------------------------
def test_main():
> assert A.variable == datetime.datetime.now()
E AssertionError: assert datetime.datetime(2020, 3, 19, 21, 32, 39, 861956) == datetime.datetime(2020, 3, 19, 3, 30)
I searched for a workaround for this and only found this question How to monkeypatch python's datetime.datetime.now with py.test?. The code included is just a minimal example of what I need. Basically I have an app who uses sqlalchemy.orm for saving data and a specific model have a default value for the date equal to datetime.datetime.now and I need to modify the date of the default value to be able to test it. The models are defined in other file.
Thank you for your help.
Doing sample code for Unit Test in Python with Hypothesis module.
Wrote simple getTimeDelta function to get time difference between two dates.
Want to write Unit Test of the getTimeDelta function. Used hypothesis modules to get multiple datetime values.
Question is that:
1. How to I pass different data type values i.e. time1 is str
and time2 is datetime without writing multiple functions?
checking only datatype of return value, not actual result is correct or not. How to do that?
How to check data types of items, of result tuple? assertIsInstance
Code:
import unittest
from hypothesis import given
from hypothesis import strategies as st
import datetime
from dateutil.parser import parse as time_parse
def getTimeDelta(time1, time2):
try:
if isinstance(time1, str):
time1 = time_parse(time1)
if isinstance(time2, str):
time2 = time_parse(time2)
return (time1 - time2, {"message": ""})
except Exception as err:
return (False, {"message": "Exception {}".format(err)})
class TestTimeDeltaCalc(unittest.TestCase):
def __init__(self, *args, **kwargs):
super(TestTimeDeltaCalc, self).__init__(*args, **kwargs)
self.utils = Utils()
#given(st.datetimes(), st.datetimes())
def test_time_delta(self, time1, time2):
time_delta = getTimeDelta(time1, time2)
if time_delta[0] is False:
self.assertIsInstance(time_delta[0], bool)
else:
self.assertIsInstance(time_delta[0], datetime.timedelta)
self.assertIsInstance(time_delta[1], dict)
#given(st.text(), st.text())
def test_time_delta(self, time1, time2):
time_delta = getTimeDelta(time1, time2)
if time_delta[0] is False:
self.assertIsInstance(time_delta[0], bool)
else:
self.assertIsInstance(time_delta[0], datetime.timedelta)
self.assertIsInstance(time_delta[1], dict)
if __name__ == '__main__':
unittest.main()
Edit 01:
We can fix #Question1 by hypothesis.strategies.one_of(*args), link
one_of is exactly the right approach.
There are many ways to test properties (see eg this article), but the easiest here is probably a "test oracle": just compare the result to a known-good implementation! Otherwise, you could eg. check that the timedelta is negative/0/positive if time1 is lt/eq/gt time2 - this is basically a lower-resolution oracle.
Your assertIsInstance calls look like they'd work to me.
Is there any way to get the total amount of time that "unittest.TextTestRunner().run()" has taken to run a specific unit test.
I'm using a for loop to test modules against certain scenarios (some having to be used and some not, so they run a few times), and I would like to print the total time it has taken to run all the tests.
Any help would be greatly appreciated.
UPDATED, thanks to #Centralniak's comment.
How about simple
from datetime import datetime
tick = datetime.now()
# run the tests here
tock = datetime.now()
diff = tock - tick # the result is a datetime.timedelta object
print(diff.total_seconds())
You could record start time in the setup function and then print elapsed time in cleanup.
Following Eric's one-line answer I have a little snippet I work with here:
from datetime import datetime
class SomeTests(unittest.TestCase):
"""
... write the rest yourself! ...
"""
def setUp(self):
self.tick = datetime.now()
def tearDown(self):
self.tock = datetime.now()
diff = self.tock - self.tick
print (diff.microseconds / 1000), "ms"
# all the other tests below
This works fine enough for me, for now, but I want to fix some minor formatting issues. The result ok is now on the next line, and FAIL has priority. This is ugly.
I do this exactly as Eric postulated -- here's a decorator I use for tests (often more functional-test-y than strict unit tests)...
# -*- coding: utf-8 -*-
from __future__ import print_function
from functools import wraps
from pprint import pprint
WIDTH = 60
print_separator = lambda fill='-', width=WIDTH: print(fill * width)
def timedtest(function):
"""
Functions so decorated will print the time they took to execute.
Usage:
import unittest
class MyTests(unittest.TestCase):
#timedtest
def test_something(self):
assert something is something_else
# … etc
# An optional return value is pretty-printed,
# along with the timing values:
return another_thing
"""
#wraps(function)
def wrapper(*args, **kwargs):
print()
print("TESTING: %s(…)" % getattr(function, "__name__", "<unnamed>"))
print_separator()
print()
t1 = time.time()
out = function(*args, **kwargs)
t2 = time.time()
dt = str((t2 - t1) * 1.00)
dtout = dt[:(dt.find(".") + 4)]
print_separator()
if out is not None:
print('RESULTS:')
pprint(out, indent=4)
print('Test finished in %s seconds' % dtout)
print_separator('=')
return out
return wrapper
That's the core of it -- from there, if you want, you can stash the times in a database for analysis, or draw graphs, et cetera. A decorator like this (using #wraps(…) from the functools module) won't interfere with any of the dark magic that unit-test frameworks occasionally resort to.
Besides using datetime, you could also use time
from time import time
t0 = time()
# do your stuff here
print(time() - t0) # it will show in seconds