3

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?

2
  • 1
    You can assign directly to sys.argv, but it's not clear how you are importing your script for testing. There's a lot of apparent duplication between your script and the test code, and no mention of where main_file is defined. Commented Jan 18, 2022 at 14:54
  • Can you explain further on how to do that? Commented Jan 18, 2022 at 16:29

2 Answers 2

2

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')
Sign up to request clarification or add additional context in comments.

Comments

1

you have to push the arguments onto sys.argv before retrieving them (if your code is pulling from command-line arguments - it's unclear to me where in your test you're using the command-line arguments but I digress)

so something like first doing

import sys

sys.argv = ['mock_filename.py', 'json_file.json']
#... continue with rest of program / test.

2 Comments

Sorry I am a little unclear what I do with the sys.argv list after defining it.
@bendowlingtech you don't do anything with it specifically. It just makes arguments available so that in your unit test when the test tries to access sys.argv the values are already there (sys.argv is a global)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.