I have a module called learning that uses random.uniform(). I have a file called test_learning.py containing unit tests. When I run a unit test, I would like the code in learning to see the patched version of random.uniform(). How can I do this? Here is what I have currently.
import random
import unittest
import unittest.mock as mock
class TestLearning(unittest.TestCase):
def test_get_random_belief_bit(self):
with mock.patch('learning.random.uniform', mock_uniform):
bit = learning.get_random_belief_bit(0.4)
self.assertEqual(bit, 0)
But the test (sometimes) fails because learning.get_random_belief_bit() seems to be using the real random.uniform().
Unit test solution:
learning.py:
import random
def get_random_belief_bit(f):
return random.uniform()
test_learning.py:
import random
import unittest
import unittest.mock as mock
import learning
class TestLearning(unittest.TestCase):
def test_get_random_belief_bit(self):
with mock.patch('random.uniform', mock.Mock()) as mock_uniform:
mock_uniform.return_value = 0
bit = learning.get_random_belief_bit(0.4)
self.assertEqual(bit, 0)
mock_uniform.assert_called_once()
if __name__ == '__main__':
unittest.main()
unit test result with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Name Stmts Miss Cover Missing
---------------------------------------------------------------------------
src/stackoverflow/57874971/learning.py 3 0 100%
src/stackoverflow/57874971/test_learning.py 13 0 100%
---------------------------------------------------------------------------
TOTAL 16 0 100%
Related
I want to mock a function present inside a list and check whether it has been called at least once. Below is a similar implementation I tried:-
In fun_list.py (funA and funB are two functions in other_module)
import other_module
FUN_LIST = [
other_module.funA,
other_module.funB,
]
def run_funs():
for fun in FUN_LIST:
fun()
In demo.py
from fun_list import run_funs
def run_demo():
...
run_funs()
...
In test_demo.py
from demo import run_demo
#patch('other_module.funB')
def test_demo_funs(mocked_funB):
mocked_funB.return_value = {}
run_demo()
assert mocked_funB.called
In above case, I'm trying to mock funB in other_module but the function doesn't get mocked and the cursor gets inside the actual funB in other_module. Thus, the assert mocked_funB.called returns false.
Any lead on how I can mock other_module.funB ?
I have found a similar question on StackOverflow but that went unanswered, so decided to post my version of it.
Any help will be appreciated, thank you in advance.
You need to mock before importing the module under test. The code in the module scope will be executed when import the module. It is too late to mock through the decorator when the test case is executed.
E.g.
other_module.py:
def funA():
pass
def funB():
pass
fun_list.py:
import other_module
print('execute module scope code')
FUN_LIST = [
other_module.funA,
other_module.funB,
]
def run_funs():
for fun in FUN_LIST:
fun()
demo.py:
from fun_list import run_funs
def run_demo():
run_funs()
test_demo.py:
import unittest
from unittest.mock import patch
class TestDemo(unittest.TestCase):
#patch('other_module.funB')
def test_demo_funs(self, mocked_funB):
print('mock before import the module')
from demo import run_demo
mocked_funB.return_value = {}
run_demo()
assert mocked_funB.called
if __name__ == '__main__':
unittest.main()
test result:
mock before import the module
execute module scope code
.
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
Name Stmts Miss Cover Missing
--------------------------------------------------------------------------
src/stackoverflow/67563601/demo.py 3 0 100%
src/stackoverflow/67563601/fun_list.py 6 0 100%
src/stackoverflow/67563601/other_module.py 4 1 75% 6
src/stackoverflow/67563601/test_demo.py 12 0 100%
--------------------------------------------------------------------------
TOTAL 25 1 96%
I took the lead from #slideshowp2's answer and have modified things a bit differently. In my case I was having multiple such test functions that is mocking funB and calling run_demo (originally a client.post() call from Django.test). If an earlier function calls it successfully, the other subsequent patches were failing (because of the same reason stated by #slideshowp2). So, I changed the approach to this :-
In fun_list.py (funA and funB are two functions in other_module)
import other_module
FUN_LIST = [
'funA',
'funB',
]
def run_funs():
for fun in FUN_LIST:
getattr(other_module, fun)()
In demo.py
from fun_list import run_funs
def run_demo():
...
run_funs()
...
In test_demo.py
from demo import run_demo
#patch('other_module.funB')
def test_demo_funs(mocked_funB):
mocked_funB.return_value = {}
run_demo()
assert mocked_funB.called
I'm having trouble testing that a method is called using mock -- as a simple example, let's say that the method is os.getcwd. I want to test that my own function, pickle_wdir, is calling os.getcwd as intended. However, the function I am testing pickles the value returned by os.getcwd, which results in an error.
Here's a simple example to reproduce the error.
os_ex.py:
import os
import pickle
def pickle_wdir(filename):
dir = os.getcwd()
with open(filename, 'wb') as handle:
pickle.dump(dir, handle)
test_os_ex.py:
from unittest import TestCase
from unittest.mock import patch
from os_ex import pickle_wdir
class TestPickleWdir(TestCase):
def test_os_called(self):
fname = 'dir.pickle'
with patch('os_ex.os') as mocked_obj:
pickle_wdir(fname)
mocked_obj.getcwd.assert_called()
The error message returned is
_pickle.PicklingError: Can't pickle <class 'unittest.mock.MagicMock'>: it's not the same object as unittest.mock.MagicMock.
How can I test that os.getcwd is called without getting this PicklingError?
You need to mock os.getcwd(), open() and pickle.dump() methods. You can use unittest.mock.patch as context manager to do this.
E.g.
os_ex.py:
import os
import pickle
def pickle_wdir(filename):
dir = os.getcwd()
with open(filename, 'wb') as handle:
pickle.dump(dir, handle)
test_os_ex.py:
import unittest
from unittest.mock import patch, mock_open
from os_ex import pickle_wdir
class TestOsEx(unittest.TestCase):
def test_pickle_wdir(self):
fname = 'dir.pickle'
m = mock_open(read_data='mocked data')
with patch('os_ex.os') as mocked_obj, patch('builtins.open', m) as mocked_open, patch('pickle.dump') as mocked_dump:
mocked_obj.getcwd.return_value = '/root'
pickle_wdir(fname)
mocked_obj.getcwd.assert_called()
m.assert_called_with(fname, 'wb')
handle = mocked_open()
mocked_dump.assert_called_with('/root', handle)
if __name__ == '__main__':
unittest.main()
unit test results with 100% coverage:
.
----------------------------------------------------------------------
Ran 1 test in 0.012s
OK
Name Stmts Miss Cover Missing
------------------------------------------------------------------------
src/stackoverflow/60627827/os_ex.py 6 0 100%
src/stackoverflow/60627827/test_os_ex.py 17 0 100%
------------------------------------------------------------------------
TOTAL 23 0 100%
With unittest and Coverage.py,
def add_one(num: int):
num = num + 1
return num
from unittest import TestCase
from add_one import add_one
class TestAddOne(TestCase):
def test_add_one(self):
self.assertEqual(add_one(0), 1)
self.assertNotEqual(add_one(0), 2)
and here is the coverage:
How can I test the whole file?
Assuming that your test file is called test_one.py run this command in the same directory:
coverage run -m unittest test_one.py && coverage report
Result should look similar to this:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Name Stmts Miss Cover
---------------------------------
add_one.py 3 0 100%
test_one.py 6 0 100%
---------------------------------
TOTAL 9 0 100%
You never call the test_add_one method.
Note how the function definition is executed, but not the body. To run your test, add a __main__ check and a TestSuite/TextTestRunner (https://docs.python.org/3/library/unittest.html)
from unittest import TestCase, TestSuite, TextTestRunner
from add_one import add_one
class TestAddOne(TestCase):
def test_add_one(self):
self.assertEqual(add_one(0), 1)
self.assertNotEqual(add_one(0), 2)
if __name__ == "__main__":
suite = TestSuite()
suite.addTest(TestAddOne("test_add_one"))
TextTestRunner().run(suite)
The result of
coverage run <file.py>
coverage html
# OR
coverage report -m
is all lines tested.
I've got the following tests.py file:
from django.test import TestCase
from lxml import etree
import tempfile
import utils
class CreateSurveyFromCsvTextTests(TestCase):
def parsesSurveyPassedInAsCsvAndReturnsXmlRepresentation(self):
text = """"survey",,,,,
,"name","type","label","hint","required"
,"gps","geopoint","Record your current location",,"false"
,"start","start",,,
,"end","end",,,
"settings",
,"form_title"
,"New survey" """
xml = create_survey_from_csv_text(text)
lxml.fromstring(xml)
when I run my module with python manage.py test, the output is
Ran 0 tests in 0.000s
I know the runner is picking up the file because if I make an invalid import it throws an error.
Why is the test not being called?
The name of the test methods need to start with test_. This allows the class to have both test methods and helper methods that you may write as well.
Hence you should rename your method to test_parsesSurveyPassedInAsCsvAndReturnsXmlRepresentation (and perhaps shorten the name too).
I'm having a small problem with my test suite with Django.
I'm working on a Python package that can run in both Django and Plone (http://pypi.python.org/pypi/jquery.pyproxy).
All the tests are written as doctests, either in the Python code or in separate docfiles (for example the README.txt).
I can have those tests running fine but Django just do not count them:
[vincent ~/buildouts/tests/django_pyproxy]> bin/django test pyproxy
...
Creating test database for alias 'default'...
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
But if I had some failing test, it will appear correctly:
[vincent ~/buildouts/tests/django_pyproxy]> bin/django test pyproxy
...
Failed example:
1+1
Expected nothing
Got:
2
**********************************************************************
1 items had failures:
1 of 44 in README.rst
***Test Failed*** 1 failures.
Creating test database for alias 'default'...
----------------------------------------------------------------------
Ran 0 tests in 0.000s
OK
This is how my test suite is declared right now:
import os
import doctest
from unittest import TestSuite
from jquery.pyproxy import base, utils
OPTIONFLAGS = (doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE)
__test__ = {
'base': doctest.testmod(
m=base,
optionflags=OPTIONFLAGS),
'utils': doctest.testmod(
m=utils,
optionflags=OPTIONFLAGS),
'readme': doctest.testfile(
"../../../README.rst",
optionflags=OPTIONFLAGS),
'django': doctest.testfile(
"django.txt",
optionflags=OPTIONFLAGS),
}
I guess I'm doing something wrong when declaring the test suite but I don't have a clue what it is exactly.
Thanks for your help,
Vincent
I finally solved the problem with the suite() method:
import os
import doctest
from django.utils import unittest
from jquery.pyproxy import base, utils
OPTIONFLAGS = (doctest.ELLIPSIS |
doctest.NORMALIZE_WHITESPACE)
testmods = {'base': base,
'utils': utils}
testfiles = {'readme': '../../../README.rst',
'django': 'django.txt'}
def suite():
return unittest.TestSuite(
[doctest.DocTestSuite(mod, optionflags = OPTIONFLAGS)
for mod in testmods.values()] + \
[doctest.DocFileSuite(f, optionflags = OPTIONFLAGS)
for f in testfiles.values()])
Apparently the problem when calling doctest.testfile or doctest.testmod is that the tests are directly ran.
Using DocTestSuite/DocFileSuite builds the list and then the test runner runs them.