Cannot mock method of mocked object? call_count is 0 in pytest - python

I have the following function in the file myfile.py:
#myfile.py
import psutil
class RunnableObject:
def run(self):
parent = psutil.Process()
print(parent)
children = parent.children(recursive=True)
print(children)
Then I have a unit test where runnable_object is an instance of the RunnableObject class which I setup using a pytest fixture.
#patch("myfile.psutil")
def test_run_post_request(self, psutil_, runnable_object):
runnable_object.run()
assert psutil_.Process.call_count == 1
assert psutil_.Process.children.call_count == 1
When I run my test however I get the following error:
assert psutil_.Process.call_count == 1
> assert psutil_.Process.children.call_count == 1
E assert 0 == 1
E +0
E -1
-1
tests/unit/test_experiment.py:1651: AssertionError
My stdout:
<MagicMock name='psutil.Process()' id='3001903696'>
<MagicMock name='psutil.Process().children()' id='3000968624'>
I also tried to use #patch.object(psutil.Process, "children") as well as#patch("myfile.psutil.Process") and #patch("myfile.psutil.Process.children") but that gave me the same problem.

children is the property of the return value of psutil.Process(). NOT the property of the Process method.
So the correct assertion is:
test_myfile.py:
from unittest import TestCase
import unittest
from unittest.mock import patch
from myfile import RunnableObject
class TestRunnableObject(TestCase):
#patch("myfile.psutil")
def test_run_post_request(self, psutil_):
runnable_object = RunnableObject()
runnable_object.run()
assert psutil_.Process.call_count == 1
assert psutil_.Process().children.call_count == 1
if __name__ == '__main__':
unittest.main()
test result:
<MagicMock name='psutil.Process()' id='4394128192'>
<MagicMock name='psutil.Process().children()' id='4394180912'>
.
----------------------------------------------------------------------
Ran 1 test in 0.002s
OK
Name Stmts Miss Cover Missing
-------------------------------------------------------------------------
src/stackoverflow/67362647/myfile.py 7 0 100%
src/stackoverflow/67362647/test_myfile.py 13 0 100%
-------------------------------------------------------------------------
TOTAL 20 0 100%

Related

Reset global variables between two tests on PyTest

I have tests on a function that uses and modifies global variables. I would like to make sure that my global variable is reset between my tests. Any trick to do that?
main.py:
y = 0
def inc(x):
# side effect
global y
y = y + 1
return x + y + 1
test_main.py:
from main import inc
def test_answer():
assert inc(3) == 5
def test_answer_again():
assert inc(3) == 5
_________________________________________________________________________________________ test_answer_again __________________________________________________________________________________________
def test_answer_again():
> assert inc(3) == 5
E assert 6 == 5
E + where 6 = inc(3)
test_main.py:8: AssertionError
====================================================================================== short test summary info =======================================================================================
FAILED test_main.py::test_answer_again - assert 6 == 5
==================================================================================== 1 failed, 1 passed in 0.01s =====================================================================================
Here is how you can use a simple fixture to ensure that the value of y is unchanged after each test:
import pytest
import main
from main import inc
#pytest.fixture
def set_y(request):
y_before = main.y
y_value_to_set = getattr(request, 'param', y_before) # optional parameter
main.y = y_value_to_set
yield # allows us to have cleanup after the test
main.y = y_before # once test is done, revert value for next test
def test_answer(set_y):
assert inc(3) == 5
def test_answer_again(set_y):
assert inc(3) == 5
#pytest.mark.parametrize('set_y', [20], indirect=["set_y"])
def test_answer_with_specific_y(set_y):
assert inc(3) == 25
You can also add the autouse=True to your fixture if you want to prevent the need to specifically mention the fixture in every test and prevent bugs due to missing to specify it:
import pytest
import main
from main import inc
#pytest.fixture(autouse=True)
def set_y(request):
y_before = main.y
y_value_to_set = getattr(request, 'param', y_before) # optional parameter
main.y = y_value_to_set
yield # allows us to have cleanup after the test
main.y = y_before # once test is done, revert value for next test
def test_answer():
assert inc(3) == 5
def test_answer_again():
assert inc(3) == 5
#pytest.mark.parametrize('set_y', [20], indirect=["set_y"])
def test_answer_with_specific_y():
assert inc(3) == 25

Problems with assert when using helper method in pytest when starting tests trough PyCharm

If the second parameter of assert is used normally it prints it out debug information.
E.g. with the following code:
class TestTest:
def test_test(self):
assert 1 == 2, "TEST"
The following debug information is printed:
tests\test_test.py:1 (TestTest.test_test)
1 != 2
Expected :2
Actual :1
<Click to see difference>
self = <tests.test_test.TestTest object at 0x000002147E5B1B08>
def test_test(self):
> assert 1 == 2, "TEST"
E AssertionError: TEST
E assert 1 == 2
E +1
E -2
test_test.py:3: AssertionError
However, if the assert happens in a helper function, this is not printed:
class TestTest:
def test_test(self):
self.verify(1)
def verify(self, parameter):
assert 1 == 2, "TEST"
results in:
tests\test_test.py:1 (TestTest.test_test)
1 != 2
Expected :2
Actual :1
<Click to see difference>
self = <tests.test_test.TestTest object at 0x000002AFDF1D5AC8>
def test_test(self):
> self.verify(1)
test_test.py:3:
So the stack trace is incomplete and the debug information not shown.
UPDATE:
This only happens when starting the test through PyCharm (2021.3).
Is there any way to fix this?

pytest - show test module name in output

How can I show the path to the test function next to the function name when a test fails? What I would like to see:
======================FAILURES==========================
_____________path/to/module::function_name______________
The headline is controlled by the head_line property of the TestReport class, although beware it's marked experimental, so it's not impossible that it may be renamed or replaced in the next versions. Create a file named conftest.py in your project root dir with the contents:
import pytest
from _pytest.reports import TestReport
class CustomReport(TestReport):
#TestReport.head_line.getter
def head_line(self):
return f'my headline: {self.nodeid}'
#pytest.hookimpl(tryfirst=True)
def pytest_runtest_makereport(item, call):
return CustomReport.from_item_and_call(item, call)
Example output:
$ pytest -v
======================================= test session starts =======================================
...
test_spam.py::test_spam PASSED [ 20%]
test_spam.py::test_eggs FAILED [ 40%]
test_spam.py::test_bacon[1] FAILED [ 60%]
test_spam.py::test_bacon[2] FAILED [ 80%]
test_spam.py::TestFizz::test_buzz FAILED [100%]
============================================ FAILURES =============================================
______________________________ my headline: test_spam.py::test_eggs _______________________________
def test_eggs():
> assert False
E assert False
test_spam.py:8: AssertionError
____________________________ my headline: test_spam.py::test_bacon[1] _____________________________
n = 1
#pytest.mark.parametrize('n', range(1,3))
def test_bacon(n):
> assert False
E assert False
test_spam.py:13: AssertionError
____________________________ my headline: test_spam.py::test_bacon[2] _____________________________
n = 2
#pytest.mark.parametrize('n', range(1,3))
def test_bacon(n):
> assert False
E assert False
test_spam.py:13: AssertionError
_________________________ my headline: test_spam.py::TestFizz::test_buzz __________________________
self = <test_spam.TestFizz object at 0x7f5e44ba2438>
def test_buzz(self):
> assert False
E assert False
test_spam.py:18: AssertionError
=============================== 4 failed, 1 passed in 0.06 seconds ================================

Comparing integer to MagicMock not working inside unittest in python

I have a class, which use a class variable to choose which logic to execute.
#in file1:
class SomeHelper():
def __init__(self):
self.my_var = 0
#in file2:
import file1
class MyClass():
...
...
def calculate():
inst = file1.SomeHelper()
if x > inst.my_var:
etc etc
I am writing a unit test and mocking SomeHelper() in another file:
from file 2 import MyClass
# tried both
#patch('file2.file1') OR #patch('file2.file1.SomeHelper')
def test_calculate(self, mock_helper):
mock_helper.my_var = 0
to_test = MyClass.calculate()
And I get the following error:
TypeError: '>' not supported between instances of 'MagicMock' and 'int'.
I thought I defined my_var after I patched the module.
Here is the unit test solution for Python 3.7.5:
file1.py:
class SomeHelper():
def __init__(self):
self.my_var = 0
file2.py:
import file1
class MyClass():
#classmethod
def calculate(cls):
x = 1
inst = file1.SomeHelper()
if x > inst.my_var:
return True
return False
test_file2.py:
import unittest
from unittest.mock import patch
from file2 import MyClass
class TestMyClass(unittest.TestCase):
#patch('file2.file1')
def test_calculate(self, mock_file1):
inst = mock_file1.SomeHelper.return_value
inst.my_var = 0.5
to_test = MyClass.calculate()
self.assertTrue(to_test)
mock_file1.SomeHelper.assert_called_once()
#patch('file2.file1')
def test_calculate_2(self, mock_file1):
inst = mock_file1.SomeHelper.return_value
inst.my_var = 2
to_test = MyClass.calculate()
self.assertFalse(to_test)
mock_file1.SomeHelper.assert_called_once()
if __name__ == '__main__':
unittest.main()
Unit test result with coverage report:
..
----------------------------------------------------------------------
Ran 2 tests in 0.002s
OK
Name Stmts Miss Cover Missing
------------------------------------------------------------------------
src/stackoverflow/50242955/file1.py 3 1 67% 3
src/stackoverflow/50242955/file2.py 8 0 100%
src/stackoverflow/50242955/test_file2.py 16 0 100%
------------------------------------------------------------------------
TOTAL 27 1 96%
Source code: https://github.com/mrdulin/python-codelab/tree/master/src/stackoverflow/50242955

pytest-mock assert_called_with failed for class function

I am planning to use pytest and pytest-mock for validating the Python code. Being a newbie, wrote a sample code to validate the mock on class and seeing failure. I am wondering what went wrong.
src/main.py
class Main(object):
def __init__(self, my_var=None):
self.var = my_var
def internal_func(self, var=10):
my_var = var + 20
return my_var
def test_func(self):
val = self.internal_func(20)
return val + 40
tests/test_main.py
import pytest
from pytest_mock import mocker
from src.main import Main
def new_func(cls, *args, **kwargs):
return 2
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
val = Main().test_func()
assert Main.internal_func.assert_called_with(20)
It fails with the following error
======================================================================================== FAILURES ========================================================================================
_____________________________________________________________________________________ test_main_mock _____________________________________________________________________________________
mocker = <pytest_mock.MockFixture object at 0x7f34f490d8d0>
def test_main_mock(mocker):
mocker.patch.object(Main, 'internal_func')
main = Main()
val = main.test_func()
# assert val == 80
> assert Main.internal_func.assert_called_with(20)
E AssertionError: assert None
E + where None = <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>>(20)
E + where <bound method MagicMock.wrap_assert_called_with of <MagicMock name='internal_func' id='139865418160784'>> = <MagicMock name='internal_func' id='139865418160784'>.assert_called_with
E + where <MagicMock name='internal_func' id='139865418160784'> = Main.internal_func
tests/test_main.py:13: AssertionError
The return_value or side_effect must be set before the patched func take effect
def test_main_mock(mocker):
# mock internal_func of class Main
mocked_internal_func = mocker.patch.object(Main, 'internal_func')
# assign return_value or side_effect
mocked_internal_func.return_value = -10
# class instance
ma = Main()
val = ma.test_func()
assert ma.internal_func.assert_called_with(20)
Correction of mistake, the assert should not be used together with assert_called_with, they are independent assert.
assert val == 30
mocked_internal_func.assert_called
ma.internal_func.assert_called_with(20)
mocked_internal_func.assert_called_with(20)

Categories