I will start off by admitting that I have just got into python mock or any mock for that matter for a day or two.
So I have a python file that has a class in it:
MyFileA.py
class A:
def Afunc():
#do smthn
Now I want to mock a different script that uses this MyFileA.py
MyFileB.py
from MyFileA import A
def Bfunc():
Aobj = A()
ReturnVal = Aobj.Afunc()
How do I mock the statement Aobj.Afunc() to return a specific value?
Also, I am exclusively using decorators for mock method so I am expecting solutions in that format only.
You can use mock.patch to mock class A and Afunc and its returned value.
E.g.
MyFileA.py:
class A:
def Afunc():
print('do smthn')
MyFileB.py:
from MyFileA import A
def Bfunc():
Aobj = A()
ReturnVal = Aobj.Afunc()
return ReturnVal
test_MyFileB.py:
import unittest
from MyFileB import Bfunc
from unittest import mock
class TestMyFileB(unittest.TestCase):
#mock.patch('MyFileB.A')
def test_Bfunc(self, mock_A):
a_instance = mock_A.return_value
a_instance.Afunc.return_value = "mocked value"
actual = Bfunc()
self.assertEqual(actual, 'mocked value')
mock_A.assert_called_once()
a_instance.Afunc.assert_called_once()
if __name__ == '__main__':
unittest.main()
unit test result with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
Name Stmts Miss Cover Missing
--------------------------------------------------------------------------
src/stackoverflow/63429593/MyFileA.py 3 1 67% 3
src/stackoverflow/63429593/MyFileB.py 5 0 100%
src/stackoverflow/63429593/test_MyFileB.py 13 0 100%
--------------------------------------------------------------------------
TOTAL 21 1 95%
Related
I'm trying to mock a class method with pytest-mock. I have the code below in a single file, and when the test is run I get ModuleNotFoundError: No module named 'RealClass' in the patch function. How to make this work?
class RealClass:
def some_function():
return 'real'
def function_to_test():
x = RealClass()
return x.some_function()
def test_the_function(mocker):
mock_function = mocker.patch('RealClass.some_function')
mock_function.return_value = 'mocked'
ret = function_to_test()
assert ret == 'mocked'
In your case since you are patching the class that is present within the test file itself you would use mocker.patch.object.
mock_function = mocker.patch.object(RealClass, 'some_function')
collected 1 item
tests/test_grab.py::test_the_function PASSED [100%]
============================== 1 passed in 0.03s ===============================
I have a file, main.py, which contains one method, main():
from grab_api_tokens import GrabApiTokens
def main()
grab_api_tokens = GrabApiTokens(site_one, site_two)
site_one_token = grab_api_tokens.login_to_site_one
site_two_token = grab_api_tokens.login_to_site_two
When trying to test this, I'm using:
import unittest
from unittest.mock import patch, Mock
import main
class TestMain(unittest.TestCase):
#patch('main.grab_apitokens.GrabApiTokens.login_to_site_two')
#patch('main.grab_apitokens.GrabApiTokens.login_to_site_one')
#patch('main.grab_apitokens.GrabApiTokens')
def test_main(self, mock_grab_api_tokens, mock_login_to_site_one, mock_login_to_site_two):
mock_grab_api_tokens = Mock(ok=True)
mock_login_to_site_one.return_value = "fake_token_one"
mock_login_to_site_two.return_value = "fake_token_two"
main.main()
self.assertTrue(mock_grab_api_tokens.called)
the problem is that no matter how I change the #patch, I can't seem to get pycharm to recognize the mocked class. Calling main() just calls the plain main method, its not patching over the values I'm specifying. I can't see where I've gone wrong after reading the unittest documentation.
The target you try to patch is not correct. Besides, you don't need to patch the properties of the GrabApiTokens class. You can patch the GrabApiTokens class and assign the fake data to the properties of its instance directly.
E.g.
main.py:
from grab_api_tokens import GrabApiTokens
def main():
site_one = '123'
site_two = '456'
grab_api_tokens = GrabApiTokens(site_one, site_two)
site_one_token = grab_api_tokens.login_to_site_one
site_two_token = grab_api_tokens.login_to_site_two
print('site_one_token: ', site_one_token)
print('site_two_token: ', site_two_token)
grab_api_tokens.py:
class GrabApiTokens():
def __init__(self, site_one, site_two) -> None:
self.login_to_site_one = site_one
self.login_to_site_two = site_two
test_main.py:
import unittest
from unittest.mock import patch, Mock
import main
class TestMain(unittest.TestCase):
#patch('main.GrabApiTokens')
def test_main(self, mock_grab_api_tokens):
mock_instance = mock_grab_api_tokens.return_value
mock_instance.login_to_site_one = "fake_token_one"
mock_instance.login_to_site_two = "fake_token_two"
main.main()
self.assertTrue(mock_grab_api_tokens.called)
if __name__ == '__main__':
unittest.main()
test result:
⚡ coverage run /Users/dulin/workspace/github.com/mrdulin/python-codelab/src/stackoverflow/66641783/test_main.py && coverage report -m --include="src/*"
site_one_token: fake_token_one
site_two_token: fake_token_two
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------------
src/stackoverflow/66641783/grab_api_tokens.py 4 2 50% 3-4
src/stackoverflow/66641783/main.py 9 0 100%
src/stackoverflow/66641783/test_main.py 13 0 100%
-----------------------------------------------------------------------------
TOTAL
I'm new to mock and really strugling with it. Mostly in documentation and most of SO pages it shows how to get the mock result_value, but I want to check if the values I'm getting from methods are correct, not the result_value's. Here's the example:
#!/usr/bin/env python
class Example:
def one(self):
return 1
def two(self, one):
print(one + 1) # basically any void method, ex: result = one + 1 and check result value if correct
def main(self):
self.two(self.one())
if __name__ == '__main__':
e = Example()
e.main()
Test:
#!/usr/bin/env python
import unittest
import example
from mock import patch
class Example(unittest.TestCase):
def test_one(self):
self.assertEqual(1, et.one())
def test_two(self):
with patch('example.Example.two'):
self.assertEqual(2, et.two(et.one())) # ..the part I'm stuck
# whick ofc throws AssertionError: 2 != <MagicMock name='two()' id='blablabla'>
def test_main(self):
# unknown part..
if __name__ == '__main__':
et = example.Example()
unittest.main()
How to achieve void method check with unittest?
UPDATE:
so the print I figured out with chepner's help:
def test_twoi3(self):
mock_print = MagicMock()
with patch('sys.stdout', mock_print):
print(2)
expected = call.write('2')
self.assertEqual(mock_print.mock_calls[0], expected)
and for main I'm not quite sure if it is a good solution...:
def test_main(self):
with patch ('example.Example.main') as m:
et.main(et.two(1))
m.assert_called_with(et.two(1))
but I want to check not by passing the methods and values, but if main calls other two methods. How to achieve this?
You don't need to mock anything (directly, anyway); you want to capture standard output and verify that it is what you expect.
from contextlib import redirect_stdout
from io import StringIO
def test_two(self):
stdout = StringIO()
with redirect_stdout(stdout):
et.two(et.one())
self.assertEqual(stdout.getvalue(), "2\n")
Or, you can mock the print and check that it is called with the expected arguments.
def test_two(self):
with patch('__builtin__.print') as p:
et.two(et.one())
p.assert_called_with(2)
I have figured out how to test if main methods have been called. Test looks like this:
#!/usr/bin/env python
import unittest
import example
from mock import patch, MagicMock, call
class Example(unittest.TestCase):
def setUp(self):
self.et = example.Example()
def test_one(self):
self.assertEqual(1, self.et.one())
def test_two(self):
mock_print = MagicMock()
with patch('sys.stdout', mock_print):
print(2)
expected = call.write('2')
self.assertEqual(mock_print.mock_calls[0], expected)
def test_main(self):
self.et.two = MagicMock(side_effect=self.et.two)
self.et.one = MagicMock(side_effect=self.et.one)
self.et.main()
self.et.one.assert_called()
self.et.two.assert_called()
self.et.one.__str__ = self.et.one
self.assertEqual(1, int(self.et.one))
if __name__ == '__main__':
unittest.main()
Upon mocking methods that are in main (all of them) and calling main methods one and two have been successfully called. For one you can return value by using __str__
Suppose I have a test class TestSuite with a method test_database_inaccessible(). I want to test a method run() in another class, AA_database.run() calls is_primary(). I can mock is_primary to return True.
I have tried
with patch.object(AADatabase, "is_primary") as is_primary_mocked:
self.dbsize = 2.1e10
self.returncode = 2
is_primary_mocked.return_value = True
self.AADatabase.run()
but I get
AttributeError: 'TestSuite' object has no attribute 'AADatabase'
Previously, I had tried simply
with patch.object(AADatabase, "is_primary") as is_primary_mocked:
self.dbsize = 2.1e10
self.returncode = 2
is_primary_mocked.return_value = True
AADatabase.run()
But I got a different error message then.
If patch is the wrong tool here, I don't mind switching to a different one. I have tried quite a few different methods.
Here is the unit test solution based on the code you provided.
main.py:
class AADatabase:
#classmethod
def is_primary(cls):
return False
#classmethod
def run(cls):
return cls.is_primary()
test_main.py:
import unittest
from main import AADatabase
from unittest.mock import patch
class TestAADatabase(unittest.TestCase):
def test_database_inaccessible(self):
with patch.object(AADatabase, 'is_primary') as is_primary_mocked:
is_primary_mocked.return_value = True
res = AADatabase.run()
self.assertTrue(res)
if __name__ == '__main__':
unittest.main()
Unit test result with coverage report:
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
OK
Name Stmts Miss Cover Missing
-----------------------------------------------------------------------
src/stackoverflow/58862981/main.py 5 1 80% 4
src/stackoverflow/58862981/test_main.py 11 0 100%
-----------------------------------------------------------------------
TOTAL 16 1 94%
Source code: https://github.com/mrdulin/python-codelab/tree/master/src/stackoverflow/58862981
I fully realize that the order of unit tests should not matter. But these unit tests are as much for instructional use as for actual unit testing, so I would like the test output to match up with the test case source code.
I see that there is a way to set the sort order by setting the sortTestMethodsUsing attribute on the test loader. The default is a simple cmp() call to lexically compare names. So I tried writing a cmp-like function that would take two names, find their declaration line numbers and them return the cmp()-equivalent of them:
import unittest
class TestCaseB(unittest.TestCase):
def test(self):
print("running test case B")
class TestCaseA(unittest.TestCase):
def test(self):
print("running test case A")
import inspect
def get_decl_line_no(cls_name):
cls = globals()[cls_name]
return inspect.getsourcelines(cls)[1]
def sgn(x):
return -1 if x < 0 else 1 if x > 0 else 0
def cmp_class_names_by_decl_order(cls_a, cls_b):
a = get_decl_line_no(cls_a)
b = get_decl_line_no(cls_b)
return sgn(a - b)
unittest.defaultTestLoader.sortTestMethodsUsing = cmp_class_names_by_decl_order
unittest.main()
When I run this, I get this output:
running test case A
.running test case B
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
indicating that the test cases are not running in the declaration order.
My sort function is just not being called, so I suspect that main() is building a new test loader, which is wiping out my sort function.
The solution is to create a TestSuite explicitly, instead of letting unittest.main() follow all its default test discovery and ordering behavior. Here's how I got it to work:
import unittest
class TestCaseB(unittest.TestCase):
def runTest(self):
print("running test case B")
class TestCaseA(unittest.TestCase):
def runTest(self):
print("running test case A")
import inspect
def get_decl_line_no(cls):
return inspect.getsourcelines(cls)[1]
# get all test cases defined in this module
test_case_classes = list(filter(lambda c: c.__name__ in globals(),
unittest.TestCase.__subclasses__()))
# sort them by decl line no
test_case_classes.sort(key=get_decl_line_no)
# make into a suite and run it
suite = unittest.TestSuite(cls() for cls in test_case_classes)
unittest.TextTestRunner().run(suite)
This gives the desired output:
running test case B
.running test case A
.
----------------------------------------------------------------------
Ran 2 tests in 0.000s
OK
It is important to note that the test method in each class must be named runTest.
You can manually build a TestSuite where your TestCases and all tests inside them run by line number:
# Python 3.8.3
import unittest
import sys
import inspect
def isTestClass(x):
return inspect.isclass(x) and issubclass(x, unittest.TestCase)
def isTestFunction(x):
return inspect.isfunction(x) and x.__name__.startswith("test")
class TestB(unittest.TestCase):
def test_B(self):
print("Running test_B")
self.assertEqual((2+2), 4)
def test_A(self):
print("Running test_A")
self.assertEqual((2+2), 4)
def setUpClass():
print("TestB Class Setup")
class TestA(unittest.TestCase):
def test_A(self):
print("Running test_A")
self.assertEqual((2+2), 4)
def test_B(self):
print("Running test_B")
self.assertEqual((2+2), 4)
def setUpClass():
print("TestA Class Setup")
def suite():
# get current module object
module = sys.modules[__name__]
# get all test className,class tuples in current module
testClasses = [
tup for tup in
inspect.getmembers(module, isTestClass)
]
# sort classes by line number
testClasses.sort(key=lambda t: inspect.getsourcelines(t[1])[1])
testSuite = unittest.TestSuite()
for testClass in testClasses:
# get list of testFunctionName,testFunction tuples in current class
classTests = [
tup for tup in
inspect.getmembers(testClass[1], isTestFunction)
]
# sort TestFunctions by line number
classTests.sort(key=lambda t: inspect.getsourcelines(t[1])[1])
# create TestCase instances and add to testSuite;
for test in classTests:
testSuite.addTest(testClass[1](test[0]))
return testSuite
if __name__ == '__main__':
runner = unittest.TextTestRunner()
runner.run(suite())
Output:
TestB Class Setup
Running test_B
.Running test_A
.TestA Class Setup
Running test_A
.Running test_B
.
----------------------------------------------------------------------
Ran 4 tests in 0.000s
OK
As stated in the name, sortTestMethodsUsing is used to sort test methods. It is not used to sort classes. (It is not used to sort methods in different classes either; separate classes are handled separately.)
If you had two test methods in the same class, sortTestMethodsUsing would be used to determine their order. (At that point, you would get an exception because your function expects class names.)