4
\$\begingroup\$

For a specific test-scenario I wanted:

  1. avoid accessing the file system through pythons builtin open-function
  2. don't want to use 3rd party libraries like Michael Foord's Mock library

I would like to hear your opinion, if this seems like a valuable solution, or if there are

  • similar solutions with less lines of code
  • grave errors or caveats in the provided solution

from io import StringIO

class MockFile(StringIO):
    """Wraps StringIO, because of python 2.6: clumsy name-property reset"""

    name = None #overwrite TextIOWrapper-property - part 1/2
    _vfs = {} #virtual File-System enables to "re-read" already written MockFiles

    def __init__(self, name, mode = 'r', buffer_ = None):
        self.name = name #overwrite TextIOWrapper-property - part 2/2
        if self._vfs.has_key(name):
            buffer_ = self._vfs[name]
        super(MockFile, self).__init__(unicode(buffer_))
        __enter__ = lambda self: self
        __exit__ = lambda self, exc_type, exc_value, traceback: None
        read = lambda self, size = None: super(Mockfile, self).read(size)

    def write(self, data):
        super(MockFile, self).write(data)
        self._vfs[self.name] = self.getvalue()

def replace_builtin(builtinname, replacementobj):
    import __builtin__
    builtin_func = getattr(__builtin__, builtinname)
    setattr(__builtin__, builtinname, replacementobj)

    def decorator_factory(func):
        def decorator(*args, **kwarg):
            return func(*args, **kwarg)

        #attach the original name and function to the decorator:
        decorator.replaced_builtin = (builtinname, builtin_func)
        return decorator
    return decorator_factory
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$
from io import StringIO

class MockFile(StringIO):
    """Wraps StringIO, because of python 2.6: clumsy name-property reset"""

    name = None #overwrite TextIOWrapper-property - part 1/2
    _vfs = {} #virtual File-System enables to "re-read" already written MockFiles

Storing files here causes them to be persistent between unit tests.

    def __init__(self, name, mode = 'r', buffer_ = None):
        self.name = name #overwrite TextIOWrapper-property - part 2/2
        if self._vfs.has_key(name):
            buffer_ = self._vfs[name]

I'd use buffer_ = self._vfs.get(name, buffer_)

        super(MockFile, self).__init__(unicode(buffer_))

What _buffer is None at this point? Won't that have strange effects?

        __enter__ = lambda self: self
        __exit__ = lambda self, exc_type, exc_value, traceback: None
        read = lambda self, size = None: super(Mockfile, self).read(size)

None of these lines of code do anything. They assign to local variables which are thrown away at the end of the function.

    def write(self, data):
        super(MockFile, self).write(data)
        self._vfs[self.name] = self.getvalue()

I wouldn't update self._vfs for every write. I'd put that in a close function. That way your tests fail if you forget to invoke close.

def replace_builtin(builtinname, replacementobj):
    import __builtin__
    builtin_func = getattr(__builtin__, builtinname)
    setattr(__builtin__, builtinname, replacementobj)

    def decorator_factory(func):
        def decorator(*args, **kwarg):
            return func(*args, **kwarg)

        #attach the original name and function to the decorator:
        decorator.replaced_builtin = (builtinname, builtin_func)
        return decorator
    return decorator_factory

I'm having real trouble figuring out how you would use this function.

UPDATE:

from io import StringIO

class MockFile(StringIO):
    """Wraps StringIO"""
    _vfs = {} #virtual File-System
    name = None #overwrite TextIOWrapper-property - part 1/2
    def __init__(self, name, mode = 'r', buffer_ = ''):
        self.name = name #overwrite TextIOWrapper-property - part 2/2
        if self._vfs.has_key(name):
            buffer_ = self._vfs.get(name, buffer_)

No need for the if. The second parameter to get is a default. Using get replaces checking for the key.

        super(MockFile, self).__init__(unicode(buffer_))

    def read(self, size = None):
        return super(MockFile, self).read(size)

    def write(self, data):
        super(MockFile, self).write(data)

These functions just call the superclass which happens anyways. So why include them?

    def close(self):
        self._vfs[self.name] = self.getvalue()
        super(MockFile, self).close()

def replace_builtin(builtinname, replacementobj):
    """makes it possible to mock the built-in functions like open.
    >>> @replace_builtin('open', MockFile)
    >>> def test_whatever(self, *args, **kwargs)
            with open(filename, 'r') as file:
                self.content = file_.read()
    """
    import __builtin__
    builtin_func = getattr(__builtin__, builtinname)

    def decorator_factory(func):
        def decorator(*args, **kwarg):
            setattr(__builtin__, builtinname, replacementobj)
            result = func(*args, **kwarg)

Bad things will happen if an exception is thrown by the function. I'd use a try/finally block here.

            setattr(__builtin__, builtinname, builtin_func)
            return result
        #attach the original name and function to the decorator:
        decorator.replaced_builtin = (builtinname, builtin_func)
        return decorator
    return decorator_factory
\$\endgroup\$
1
  • \$\begingroup\$ Have there been updates to this in the past 9+ years? I am trying to do a similar thing today. do I just copy the blocks after the "Update" above and put them together? \$\endgroup\$
    – RufusVS
    Jul 13, 2021 at 19:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.