mock/patch os.path.exists with multiple return values

13,002

So after a bit more research and trial and error, with most of the examples here: http://www.voidspace.org.uk/python/mock/patch.html, I solved my problem.

import mock
import os

def side_effect(arg):
    if arg == 1:
        return True
    else:
        return False

class TestClass(unittest.TestCase):
    patcher = mock.patch('os.path.exists')
    mock_thing = patcher.start()
    mock_thing.side_effect = side_effect
    arg_list = [1, 2]
    ret = test(arg_list)
    self.assertItemsEqual([1], ret)

test calls os.path.exist for each item in arg_list, and returns a list of all items that os.path.exist returned True for. This test now passes how I want it.

Share:
13,002
Yep_It's_Me
Author by

Yep_It's_Me

I've been working as a web developer since 2014 and have recently join HyraIQ as a co-founder.

Updated on June 05, 2022

Comments

  • Yep_It's_Me
    Yep_It's_Me almost 2 years

    I'm trying to test a function that I made that iterates through a list, and calls os.path.exists for each item in the list. My test is passing the function a list of 2 objects. I need os.path.exists to return True for one of them and False for the other. I have tried this:

    import mock
    import os
    import unittest
    
    class TestClass(unittest.TestCase):
        values = {1 : True, 2 : False}
        def side_effect(arg):
            return values[arg]
    
        def testFunction(self):
            with mock.patch('os.path.exists') as m:
                m.return_value = side_effect # 1
                m.side_effect = side_effect # 2
    
                arglist = [1, 2]
                ret = test(argList)
    

    Using either but not both of line #1 and #2 give NameError: global name 'side_effect' is not defined

    I found this question and modified my code like so:

    import mock
    import os
    
    class TestClass(unittest.TestCase):
        values = {1 : True, 2 : False}
        def side_effect(arg):
            return values[arg]
    
        def testFunction(self):
            mockobj = mock(spec=os.path.exists)
            mockobj.side_effect = side_effect
    
            arglist = [1, 2]
            ret = test(argList)
    

    And this produces TypeError: 'module' object is not callable. I also tried switching these lines:

    mockobj = mock(spec=os.path.exists)
    mockobj.side_effect = side_effect
    

    for this

    mockobj = mock(spec=os.path)
    mockobj.exists.side_effect = side_effect
    

    and this

    mockobj = mock(spec=os)
    mockobj.path.exists.side_effect = side_effect
    

    with the same error being produced. Can anyone point out what it is that I am doing wrong and what I can do to get this to work?

    EDIT: After posting my answer below I realised that my first bit of code actually works as well, I just needed m.side_effect = TestClass.side_effect instead of m.side_effect = side_effect.

  • hithwen
    hithwen about 10 years
    And what if the function has the same argument on both calls?
  • Yep_It's_Me
    Yep_It's_Me about 10 years
    What do you mean? If os.path.exists is passed a 1 for both calls? Then it will return True because that's what the side effect says. But it won't be passed the same arg twice because the test function iterates through arg_list and calls os.path.exists for each element in the list. So as long as both elements in the list aren't 1 then it will work as expected.
  • hithwen
    hithwen about 10 years
    I meant, if I want to call os.path.exists two times with the same argument and return first False and then True. I figured out how to do it defining a function that will count how many times have been called and returning different values and using it as a side_effect.