Mocking a method which calls another method - python

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

Related

Mock testing with nested function changes

I am adding testing to a pipeline project, code is already written and in production so it cannot be changed to accommodate the tests.
In simplest terms, if I have a function like so:
def other_foo():
return 1
def foo():
res = other_foo()
return res
In practicality, the other_foo call will return a variety of responses, but for testing, I want to create a fixed response to test foo.
So in my test I want to create a fixed response to other_foo of 2. and my test evaluation to be something like:
def test_foo():
# some mocking or nesting handle here for other_foo
res = foo()
assert res == 2
Use the patch decorator from unitest.mock and patch your module local variable.
from your.module import foo
from unitest.mock import patch
#patch('your.module.other_foo')
def test_foo(mock_other_foo):
mock_other_foo.return_value = 3
assert foo() == 3
mock_other_foo.return_value = 42
assert foo() == 42
You can find more information here and there.

Mocking class method with pytest-mock returns error

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 ===============================

Mocked class and methods not being patched

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

How to mock a function called by an object of a class?

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%

How to run unittest test cases in the order they are declared

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.)

Categories

Resources