7

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.

Community
  • 1
  • 1
Yep_It's_Me
  • 4,494
  • 4
  • 43
  • 66
  • It's better to isolate problems and to create minimal working examples (MWE). Please learn scopes in Python (I provide the link, https://docs.python.org/3/reference/executionmodel.html#resolution-of-names). This is a very basic thing in Python, and sorry for downvote. – Yaroslav Nikitenko Jan 16 '21 at 16:01
  • Is the provided TestClass not an MWE? – Yep_It's_Me Jan 19 '21 at 03:46
  • 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 Jan 19 '21 at 08:39
  • 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 Jan 19 '21 at 22:53
  • Either way, thanks for the feedback, and for leaving a comment explaining the downvote. – Yep_It's_Me Jan 19 '21 at 22:53
  • You are welcome. "I think that os.path.exists is behaving differently" - I can't see how it can behave differently when there is an error. The question is not about nuances of error reporting. As I wrote, the first example shows lack of very basic knowledge of Python. If you really care about the downvote, please just remove that (so that there is less mess here), and I shall remove the downvote. I upvoted your answer though. – Yaroslav Nikitenko Jan 20 '21 at 11:20

2 Answers2

7

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.

markmuetz
  • 9,334
  • 2
  • 32
  • 33
Yep_It's_Me
  • 4,494
  • 4
  • 43
  • 66
  • And what if the function has the same argument on both calls? – hithwen Mar 21 '14 at 10:23
  • 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. – Yep_It's_Me Mar 23 '14 at 04:04
  • 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. – hithwen Mar 24 '14 at 11:38
-1

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

kio
  • 374
  • 1
  • 10