Unexpected behavior from unittest.mock.patch - python

What is wrong with my code below?
I'm expecting assert call_func_once_with("b") to throw an error as call_func was passed 'a'. I confimed that the function was indeed called once and with argument 'a'.
from unittest.mock import Mock, patch
def call_func(x):
pass
#patch("__main__.call_func")
def test_call_func(call_func):
call_func("a")
assert call_func.called_once_with("b")
assert call_func.called == 1
print(call_func.call_args)
test_call_func()
Output:
call('a')

You're not the first person to notice strange things with these types of assertions (see Magic mock assert_called_once vs assert_called_once_with weird behaviour)
For what it's worth, I can only advise that you try to create a test class which inherits from unittest.TestCase and then use the assertEqual method to get more consistent test behaviour:
import unittest
from unittest.mock import patch, call
def call_func(x):
pass
class MyTests(unittest.TestCase):
#patch("__main__.call_func")
def test_call_func(self, call_func_mock):
call_func_mock("a")
# assert call_func_mock.called == 1
# assert call_func_mock.called_once_with("b")
self.assertEqual(call_func_mock.call_count, 1)
self.assertEqual(call_func_mock.call_args_list[0], call("b"))
print(call_func_mock.call_args)
unittest.main()
This gives the following (expected) results:
F
======================================================================
FAIL: test_call_func (__main__.MyTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "C:\Python36\lib\unittest\mock.py", line 1179, in patched
return func(*args, **keywargs)
File "C:/scratch.py", line 16, in test_call_func
self.assertEquals(call_func_mock.call_args_list[0], call("b"))
AssertionError: call('a') != call('b')
----------------------------------------------------------------------
Ran 1 test in 0.003s
FAILED (failures=1)
Process finished with exit code 1

Related

Disable decorator for unittest in Python

How to disable decorator during project unittests in Python?
When running the unittests, I would like to disable the selected decorator for the duration of the tests.
import unittest
import functools
def handle_value_error(func):
#functools.wraps(func)
def wrapper_handle_value_error():
try:
return func()
except ValueError as e:
print(f"During calling {func.__name__!r} an error was raised: {str(e)}")
return wrapper_handle_value_error
#handle_value_error
def func_that_raise_value_error():
# do something
raise ValueError
def disable_decorator_for(func, decorator):
# do something that disable handle_value_error decorator
pass
class TestFunc(unittest.TestCase):
def test_func_that_raise_value_error(self):
disable_decorator_for(func=func_thar_raise_value_error, decorator=handle_value_error)
with self.assertRaises(ValueError):
func_thar_raise_value_error()
When function disable_decorator_for() is called, the tests should pass, but the output is:
During calling 'func_thar_raise_value_error' an error was raised:
Failure
Traceback (most recent call last):
File "...\disable_decorator_for_unittests.py", line 29, in test_func_thar_raise_value_error
with self.assertRaises(ValueError):
AssertionError: ValueError not raised
Ran 1 test in 0.003s
FAILED (failures=1)
Process finished with exit code 1

Mocking time.sleep will cause the test to fail

I wrote this test, but in order not to delay the test, I mock the time.sleep and the test will encounter an fail.
from unittest.mock import patch
from django.core.management import call_command
from django.db.utils import OperationalError
from django.test import TestCase
class CommandsTest(TestCase):
#patch('time.sleep', return_value=None)
def test_wait_for_db(self):
"""Test waiting for db"""
with patch('django.utils.connection.BaseConnectionHandler.__getitem__') as gi:
gi.side_effect = [OperationalError] * 5 + [True]
call_command('wait_for_db')
self.assertEqual(gi.call_count, 6)
By commenting on this second line(#patch), the program will run properly.
here is the error:
ERROR: test_wait_for_db (core.tests.test_commands.CommandsTest)
Test waiting for db
----------------------------------------------------------------------
Traceback (most recent call last):
File "/usr/local/lib/python3.10/unittest/mock.py", line 1369, in patched
return func(*newargs, **newkeywargs)
TypeError: CommandsTest.test_wait_for_db() takes 1 positional argument but 2 were given
You'll need to add an argument to your test_wait_for_db.
Since you use a decorator, the mocked function is passed as argument to that function
class CommandsTest(TestCase):
#patch('time.sleep', return_value=None)
def test_wait_for_db(self, mocked_sleep):
In your test you can then test assert if indeed was called. More information here.

Creating a Unit testcase Class within a function not running testcases

I need to create a unit test class with a function so i can call the function when some event is triggered. I am using the below method but testcases are not executing
gp = r"somefile"
def MyFunc():
if os.path.exists(gp):
print("yes")
class First__Test_Cases(unittest.TestCase):
def test_001(self):
print("1")
def test__002(self):
print("2")
if __name__ == '__main__':
unittest.main()
First__Test_Cases()
else:
print("fail")
MyFunc()
output - Ran 0 tests in 0.000s
Remove MyFunc() and global parts, it should only contain class and main
mytestfile.py
import unittest
class First_Test_Cases(unittest.TestCase):
def test_001(self):
Pass
def test__002(self):
Pass
if __name__ == '__main__':
unittest.main()
Then run
python mytestfile.py
And all tests in the class will be executed:
...
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
You can read more and see more examples in the documentation
If you need to have a function call the test, you should do that in a separate file. Check this post: Run unittests from a different file
According to what I understood from your change in code, this is your use case:
Tests should be run only if a certain file exists. Otherwise, they should be skipped.
For this use case, I would suggest the following solution:
import os
import unittest
gp = "some_file.txt"
msg = "file {} does not exist".format(gp)
class First_Test_Cases(unittest.TestCase):
#unittest.skipUnless(os.path.exists(gp), msg)
def test_001(self):
pass
#unittest.skipUnless(os.path.exists(gp), msg)
def test_002(self):
pass
if __name__ == '__main__':
unittest.main()
The output would be the following if the file does not exist:
ss
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK (skipped=2)
and this one, if it exists:
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
In case if you want your tests to fail, just change the code in this way:
import os
import unittest
gp = "some_file.txt"
msg = "file {} does not exist".format(gp)
class First_Test_Cases(unittest.TestCase):
def test_001(self):
self.assertTrue(os.path.exists(gp), msg) # add this line
# your code
def test_002(self):
self.assertTrue(os.path.exists(gp), msg) # add this line
# your code
Then, the output would be the following:
FF
======================================================================
FAIL: test_001 (__main__.First_Test_Cases)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 11, in test_001
self.assertTrue(os.path.exists(gp), msg)
AssertionError: file some_file.txt does not exist
======================================================================
FAIL: test_002 (__main__.First_Test_Cases)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/tmp/test.py", line 15, in test_002
self.assertTrue(os.path.exists(gp), msg)
AssertionError: file some_file.txt does not exist
----------------------------------------------------------------------
Ran 2 tests in 0.000s
FAILED (failures=2)

unittest received None from iterator instead of a raised error

I am still learning unittest and therefore, am unable to tell if there's a something missing in my test case in test_iterators.py below. Can someone help me to understand why the ValueError failed to be raised within unittest? Here are the scripts:
iterators.py
"""
Simple class to count from zero to N
"""
class count_to(object):
def __init__(self, nber):
self.nber = nber
def __iter__(self):
return count_to_iter(self.nber)
class count_to_iter(object):
def __init__(self, nber):
self.stopat = nber
self.current_nber = 0
def __next__(self):
if self.stopat < 0:
raise ValueError
elif self.current_nber > self.stopat:
raise StopIteration
self.current_nber += 1
return self.current_nber - 1
if __name__ == '__main__':
for x in count_to(-1):
print(x)
tests/test_iterators.py
import unittest
import iterators
class TestBaseIterators(unittest.TestCase):
def setUp(self):
pass
# Can't get the negative test right yet. It returns None instead of raising a ValueError
# Calling iterators.py directly and execute main successfully raised a ValueError however
def test_negative(self):
with self.assertRaises(ValueError): iterators.count_to(-1)
if __name__ == '__main__':
unittest.main()
I have used a similar approach to test raised errors previously and it worked. However, for this particular test case, here's what I get from the test.
test_negative (test_iterators.TestBaseIterators) ... FAIL
NoneType: None
======================================================================
FAIL: test_negative (test_iterators.TestBaseIterators)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/kerwei/Git/Concepts/tests/test_iterators.py", line 19, in test_negative
with self.assertRaises(ValueError): iterators.count_to(-1)
AssertionError: ValueError not raised
----------------------------------------------------------------------
Ran 1 test in 0.004s
FAILED (failures=1)
If I were to call iterators directly from __main__, I can then successfully receive the ValueError.
(py36) Kers-MacBook-Air:Concepts kerwei$ python iterators.py
Traceback (most recent call last):
File "iterators.py", line 29, in <module>
for x in count_to(-1):
File "iterators.py", line 19, in __next__
raise ValueError
ValueError
count_to(-1) creates a new count_to instance, it does not iterate over it, but you placed the test on self.stop_at value and raise the ValueError in the count_to_iter.__next__ method, so you will obviously not get a ValueError until you iterate on the count_to instance.
The naive fix would be to force iteration, ie:
def test_negative(self):
with self.assertRaises(ValueError):
# passing the iterable to `list` will force iteration
list(iterators.count_to(-1))
But the root problem is actually more of a design issue: raising a ValueError at this point is far from optimal since it will only happen when actually consuming the iterable, so you will have to inspect the call stack up until you find where count_to has been passed a wrong value. A much better solution is to check the value and eventually raise directly at the point where count_to is instanciated so it breaks always and immediatly (instead of "eventually, when you try to use the iterator in some possibly remote part of the code):
class count_to(object):
def __init__(self, nber):
if nber < 0:
raise ValueError("count_to argument must be a positive integer")
self.nber = nber
def __iter__(self):
return count_to_iter(self.nber)
And then your current test code will work as intended.

Running unittest.main() from a module?

I wrote a little function that dynamically defines unittest.TestCase classes (trivial version below).
When I moved it out of the same source file into its own module, I can't figure out how to get unittest to discover the new classes. Calling unittest.main() from either file doesn't execute any tests.
factory.py:
import unittest
_testnum = 0
def test_factory(a, b):
global _testnum
testname = 'dyntest' + str(_testnum)
globals()[testname] = type(testname, (unittest.TestCase,), {'testme': lambda self: self.assertEqual(a, b)})
_testnum += 1
def finish():
unittest.main()
someotherfile.py:
from factory import test_factory, finish
test_factory(1, 1)
test_factory(1, 2)
if __name__ == '__main__':
finish()
Output:
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
So it doesn't execute any tests.
Note that keeping it all in the same file works as expected:
import unittest
_testnum = 0
def test_factory(a, b):
global _testnum
testname = 'dyntest' + str(_testnum)
globals()[testname] = type(testname, (unittest.TestCase,), {'testme': lambda self: self.assertEqual(a, b)})
_testnum += 1
test_factory(1, 1)
test_factory(1, 2)
if __name__ == '__main__':
unittest.main()
Output (as expected):
.F
======================================================================
FAIL: testme (__main__.dyntest1)
----------------------------------------------------------------------
Traceback (most recent call last):
File "partb.py", line 11, in <lambda>
globals()[testname] = type(testname, (unittest.TestCase,), {'testme': lambda self: self.assertEqual(a, b)})
AssertionError: 1 != 2
----------------------------------------------------------------------
Ran 2 tests in 0.008s
FAILED (failures=1)
How I use my test_factory() function such that I can execute all of the TestCase objects it defines from a separate source file?
The general idea (what unittest.main does for you) is:
suite = unittest.TestLoader().loadTestsFromTestCase(SomeTestCase)
unittest.TextTestRunner(verbosity=2).run(suite)
as per http://docs.python.org/library/unittest.html?highlight=unittest#module-unittest . Your test cases are hidden in globals() by the test_factory function, so just do a dir(), find the globals that are instances of unittest.TestCase (or ones with names starting with 'dyntest', etc), and just build your suite that way and run it.
By default, unittest.main() looks for unit TestCase objects in the main module. The test_factory creates the TestCase objects in its own module. That's why moving it outside of the main module causes the behavior you see.
Try:
def finish():
unittest.main(module=__name__)

Categories