0

How can I mock exists() for certain paths only while making it do the real thing for any other path?

For example the class under test will call exists() and would fail on the paths that were supplied to it, because they do not exist on the system where the tests are running.

With Mox one could completely stub out exists(), but this would make the test fail because calls not related to the class under test will not act in the real way.

I guess I could use WithSideEffects() to call my own function when exists() is being called branching the call in two directions, but how can I access the original exists()?

This is what I have so far:

def test_with_os_path_exists_partially_mocked(self):

    self.mox.StubOutWithMock(os.path, 'exists')

    def exists(path):
        if not re.match("^/test-path.*$", path):
            return call_original_exists_somehow(path)
        else:
            # /test-path should always exist
            return True

    os.path.exists(mox.Regex("^.*$")).MultipleTimes().WithSideEffects(exists)

    self.mox.ReplayAll()

    under_test.run()

    self.mox.VerifyAll()
try-catch-finally
  • 7,436
  • 6
  • 46
  • 67

1 Answers1

0

Mox internally uses "Stubout" for the actual stubbing:

Mox.__init__()
    self._mock_objects = []
    self.stubs = stubout.StubOutForTesting()


Mox.StubOutWithMock()
    ...
    self.stubs.Set(obj, attr_name, stub)

Stubout saves the stub in an internal collection:

StubOutForTesting.Set():
    ...
    self.cache.append((parent, old_child, child_name))

Since the return value of intermediate calls is cached in the mocked method object, it has to be reset in a side effect callback.

When a method mock is being created, it will be pushed to will be stored in _expected_calls_queue. In replay mode, an expected call is repesented by a MultipleTimesGroup instance which will track calls to each method referenced in _methods.

So, one can refer to the origial method by navigating Mox.stubs.cache.

This example will mock exists() passing through calls to the original function if they do not start with /test-path, any other call will always return True.

class SomeTest(unittest.TestCase):

    def setUp(self):
        self.mox = mox.Mox()

    def tearDown(self):
        self.mox.UnsetStubs()

    def test_with_os_path_exists_partially_mocked(self):

        self.mox.StubOutWithMock(os.path, 'exists')

        # local reference to Mox
        mox_ = self.mox

        # fake callback
        def exists(path):
            # reset returnvalues of previous calls
            # iterate mocked methods. the indices denote
            # the mocked object and method and should
            # have the correct values
            for method in mox_._mock_objects[0]._expected_calls_queue[0]._methods:
                method._return_value = None

            if not re.match("^/test-path.*$", path):
                # call real exists() for all paths not
                # starting with /test-path

                # lookup original method:
                # - filter by name only (simplest matching)
                # - take the 2nd value in the tupel (the function)
                orig_exists = filter(lambda x: x[2] == "exists", mox_.stubs.cache)[0][1]
                # call it
                return orig_exists(path)
            else:
                # hardcoded True for paths starting with /test-path
                return True

        # expect call with any argument, multiple times, and call above fake
        os.path.exists(mox.Regex("^.*$")).MultipleTimes().WithSideEffects(exists)

        self.mox.ReplayAll()

        # test goes here

        self.mox.VerifyAll()
try-catch-finally
  • 7,436
  • 6
  • 46
  • 67