TAGS :Viewed: 16 - Published at: a few seconds ago

[ Python: Returning tuple for os.walk in unittest ]

I am writing a unit test for the following function:

def create_zip(path,zipf):
    #Create zip files
    for root, dirs, files in os.walk(path):
        for file in files:
            #write all json files to a zip file
            for name in glob.glob('*.json'):
                os.chdir(root)
                #write files to zip file
                zipf.write(name)
                #Remove files after creating zip file
                os.remove(name)

This is what I have at the moment:

tmpfilepath = os.path.join(tempfile.gettempdir(), "tmp-testfile")
@mock.patch('my_script.os.walk')
def test_create_basic_zip(self,mock_os_walk):
    mock_os_walk.return_value = ('test1','test2','test3')
    zipf = zipfile.ZipFile(self.tmpfilepath, 'w', zipfile.ZIP_DEFLATED)
    files = my_script.create_zip('.',zipf)

However I keep getting the error:

for root, dirs, files in os.walk(path):
ValueError: too many values to unpack

or when I change it to:

mock_os_walk.return_value = (['test1'],['test2'],['test3'])

I get the error:

ValueError: need more than 1 value to unpack

Where am I going wrong?

Answer 1


In order to match the os.walk() result format, you need to have an iterable where each item consists of 3 elements - the root, dirs and files. Replace:

mock_os_walk.return_value = ('test1','test2','test3')

with, for example:

mock_os_walk.return_value = [('./test1', ['test2', 'test3'], ['test4.txt']), ]

Answer 2


Your mock of os.walk needs to return an iterable, since you are doing for ... in os.walk(...):

It turns out that a tuple is an iterable. So when you set ...mock.return_value = (...) you are defining the iterable that will be returned.

You call os.walk, and get a tuple back. But you called it in the context of a for loop, so effectively you have written:

for a,b,c in ('test1', 'test2', 'test3'):

Which expands to:

a,b,c = 'test1'

the first time through the loop.

What you really want is to provide an iterable that gives a value for each of the variables, at least once and possibly more. This code works, and I think you can figure out the rest:

#!python3
from unittest import mock
import os


def other_function():
    for a,b,c in os.walk('.'):
        print(a,b,c)

@mock.patch('os.walk')
def test_code(mock_os_walk):
    mock_os_walk.return_value = ((1,2,3), (4,5,6), (7,8,9))
    other_function()

test_code()