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