How do I patch a sys attribute using a decorator?

旧时模样 提交于 2019-12-24 21:26:22

问题


I have a function which depends on the Python version. I would like to test this in a unittest by mocking sys.version info. The following code works:

def test_python_version(self):
    with patch("sys.version_info", [3]):
        my_func()
    with patch("sys.version_info", [2]):
        my_func()

I would like to use a decorator, but the following code does not work. Why is this? How do I set the value of the MagicMock object that gets passed into my test? Thanks

@patch("sys.version_info")
def test_python_version(self, mock_version_info):
        mock_version_info.return_value = [3]
        my_func()
        mock_version_info.return_value = [2]
        my_func()

Fails with TypeError: '<' not supported between instances of 'MagicMock' and 'int' when my_func tries to do if sys.version_info[0] < 3.


回答1:


Edit:

Yes it works. The original function returns a named tuple with major, minor and micro fields. You can mimic it by building your own named tuple. I just use a simple tuple as you access with int index instead. Problem with your code is in the way you indexed with [0] which was not right.

Edit2:

As Zaur Nasibov pointed out sys.version_info is not a function, and so my code was wrong despite looking fine with the mocks (as GenError also found). I have done a little change to fix it (see GenError answer for an alternative using PropertyMock)

import sys
from unittest.mock import patch

def my_func():
    version = sys.version_info # removed the ()
    print('Detected version:', version)
    if version[0] < 3:
        print('Error: Python 3 required.')

@patch('__main__.sys')
def test_python_version(mock_sys):
        mock_sys.version_info = (3,6,2)
        my_func()
        print()
        mock_sys.version_info = (2,7,0)
        my_func()


test_python_version()

Outputs

Detected version: (3, 6, 2)

Detected version: (2, 7, 0)
Error: Python 3 required.



回答2:


I found the answer by progmatico but it has a serious issue for me, as it requires to call sys.version_info() instead of sys.version_info which would break the code if ran normally.

Example:

#filename: a.py
import sys
def do_something():
    if sys.version_info > (3,5):
        print('Python 3.5 or newer')
    else:
        print('Python pre 3.5')

Now if I want to test both cases in a unit test @patch("sys.version_info") will lead to an error as given by OP. Changing do_something() to use sys.version_info() would break it if the mock is not used.

Test file:

#filename: test_a.py
from unittest.mock import patch, PropertyMock
import a

@patch('a.sys')
def test_a(mock_sys):
    type(mock_sys).version_info = PropertyMock(return_value=(3,4))
    a.do_something() # will show mocked python version

So you have to mock the sys module imported in the module a and set a PropertyMock on it.



来源:https://stackoverflow.com/questions/52928397/how-do-i-patch-a-sys-attribute-using-a-decorator

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!