The context is I'm trying to unit test some Python code that involves a Python version being checked for. I had this working under the platform
approach, but I was told using platform
is not a great idea and I should use sys.version_info
instead. Thus I have the following logic:
def main() -> None:
python_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
if float(python_version) < 3.7:
sys.stderr.write("\nQuendor requires Python 3.7 or later.\n")
sys.stderr.write(f"Your current version is {python_version}\n\n")
sys.exit(1)
(I do realize I could also do if sys.version_info < (3, 7):
for my check.)
My unit test is this:
def test_bad_python_version(capsys) -> None:
import sys
from quendor.__main__ import main
with mock.patch.object(sys, "version_info") as v_info:
v_info.major = 3
v_info.minor = 5
main()
terminal_text = capsys.readouterr()
print(terminal_text)
Here my test isn't complete, of course. I'm trying to print the output just to make sure the test is working.
Running that test gets me this:
ValueError: could not convert string to float:
"<MagicMock name='version_info.__getitem__()'
id='1417904973808'>.<MagicMock name='version_info.__getitem__()'
id='1417904973808'>"
Incidentally, if my switch my condition in main
to this (if sys.version_info < (3, 7):
) I get a similar but different error:
TypeError: '<' not supported between instances of 'MagicMock' and 'tuple'
I'm not sure how to get the version_info passed in correctly, which I think is my issue here. I have searched the documentation and I've seen other questions here (example: How to unit test a python version switch) about MagicMock but nothing that just definitively states: "This is how to do this."
I should note that I had a version working perfectly fine when using platform
. That version of my test looked like this:
with mock.patch.object(
platform,
"python_version",
return_value="3.5",
), pytest.raises(SystemExit) as pytest_wrapped_e:
main()
terminal_text = capsys.readouterr()
expect(terminal_text.err).to(contain("Quendor requires Python 3.7"))
Key to that seemed to be the "return_value" I was using.
CodePudding user response:
In your unit test you are setting/mocking the version by doing
v_info.major = 3
v_info.minor = 5
but accessing sys.version_info[0]
or sys.version_info[1]
.
Try to change your version extraction in main()
to
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
Worked for me:
In [8]: with mock.patch.object(sys, "version_info") as v_info:
...: v_info.major = 3
...: v_info.minor = 5
...: main()
...:
Quendor requires Python 3.7 or later.
Your current version is 3.5
CodePudding user response:
You are patching it this way:
with mock.patch.object(sys, "version_info") as v_info:
v_info.major = 3
v_info.minor = 5
Thus, it will be applied to the following calls:
sys.version_info.major # Will display 3
sys.version_info.minor # Will display 5
But instead, what you accessed in the source code was different:
sys.version_info
sys.version_info[0]
sys.version_info[1]
Obviously, your patches wouldn't reflect because they are targeted to .major
and .minor
but those aren't called anyways. Instead, change the patch to be applied to sys.version_info
which should return a tuple of items (3, 5)
so that it would reflect since it's the one called in your source code. No need to change anything from the source code:
src.py
import sys
def main() -> None:
print("Call main")
python_version = f"{sys.version_info[0]}.{sys.version_info[1]}"
if float(python_version) < 3.7:
print("Quendor requires Python 3.7 or later.")
def main2() -> None:
print("Call main2")
if sys.version_info < (3, 7):
print("Quendor requires Python 3.7 or later.")
test_src.py
import sys
from unittest import mock
from src import main, main2
def test_bad_python_version():
with mock.patch.object(sys, "version_info", (3, 5)) as v_info:
main()
main2()
def test_good_python_version():
with mock.patch.object(sys, "version_info", (3, 8)) as v_info:
main()
main2()
Output
$ pytest -q -rP
.. [100%]
============================================== PASSES ===============================================
______________________________________ test_bad_python_version ______________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Call main
Quendor requires Python 3.7 or later.
Call main2
Quendor requires Python 3.7 or later.
_____________________________________ test_good_python_version ______________________________________
--------------------------------------- Captured stdout call ----------------------------------------
Call main
Call main2
2 passed in 0.06s