4
votes

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.

2
It's better to isolate problems and to create minimal working examples (MWE). Please learn scopes in Python (I provide the link, docs.python.org/3/reference/…). This is a very basic thing in Python, and sorry for downvote. - Yaroslav Nikitenko
Is the provided TestClass not an MWE? - Yep_It's_Me
Maybe you are right that for unittest this class is an MWE. I use pytest, and one function is enough for that. However I call it not MWE because there are two classes, not one. In the first example the error has nothing to do with mock. In the second too, because if you run mock(spec=os.path.exists) (after importing mock) in the interpreter, that won't work with the same error. - Yaroslav Nikitenko
Take your point that the test/mock actually has nothing to do with the error. However I didn't know that at the time, hence the SO question. Also the two classes you see are actually the same class, just with a couple of modifications to show why I think that os.path.exists is behaving differently. If I had only shown one of them, it would not be an MWE because it would not illustrate os.path.exists behaving differently. - Yep_It's_Me
Either way, thanks for the feedback, and for leaving a comment explaining the downvote. - Yep_It's_Me

2 Answers

5
votes

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.

-1
votes

you could have done self.side_effect I believe. since the initial definition was not global, calling side_effect looks inside the global scope