How to unittest with command line arguments

I am using the python unittest module for testing a file that takes a command line argument. The argument is a file name which is then passed into a function like so:

file_name = str(sys.argv[1])
file = open(file_name)
result = main_loop(file)
print(result)

My test is set up like so:

class testMainFile(unittest.TestCase):

    def test_main_loop(self):
        file = open('file_name.json')
        result = main_file.main_loop(file)
        self.assertEqual(result, 'Expected Result')
                
if __name__ == 'main':
    unittest.main()

When I run the test I get an "IndexError: list index out of range".

I tried passing the argument when running the test but to no avail. How do I run my test without error?


I think you have couple of options here. Firstly go to documentation and checkout patch because i think you can get away with

from unittest.mock import patch

@patch('sys.argv', ['mock.py', 'test-value'])
def test_main_loop(self):

Options for fun:

One would be simply to override the sys.argv next to your call

def test_main_loop(self):
    file = open('file_name.json')
+   orginal_argv = sys.argv
+   sys.argv = ['mock argv', 'my-test-value'] 
    result = main_file.main_loop(file)
+   sys.argv = orginal_argv 
    self.assertEqual(result, 'Expected Result')

Second would be to create a simple wrapper for your function

def set_sys_argv(func: Callable):
    sys.argv = ['mock.py', 'my_test_value']
    def wrapper(*args, **kwargs):
        func()
    return wrapper

and use it with test function

@set_sys_argv
def test_main_loop(self):

We can improve it slightly and make it more generic making a decorator that accepts the values to mock

def set_sys_argv(*argv):
    sys.argv = argv

    def _decorator(func: Callable):
        def wrapper(*args, **kwargs):
            func()
        return wrapper
    return _decorator

and use it similarly to patch

@set_sys_argv('mock.py', 'test-value')
def test_main_loop(self):

Third would be to create a context manager, likewise:

class ReplaceSysArgv(list):
    def __enter__(self):
        self._argv = sys.argv
        sys.argv = ['mock', 'my-test-value']
        return self
    def __exit__(self, *args):
        sys.argv = self._argv

and use it with your code

    def test_main_loop(self):
        file = open('file_name.json')
        with ReplaceSysArgv():
            result = main_file.main_loop(file)
        self.assertEqual(result, 'Expected Result')