2

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?

asked Jan 18, 2022 at 14:46
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')
answered Jan 18, 2022 at 15:27
Sign up to request clarification or add additional context in comments.

Comments

0

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.
answered Jan 18, 2022 at 14:55

1 Comment

Sorry I am a little unclear what I do with the sys.argv list after defining it.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

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.