HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

Monkeypatching builtin open and File Mock-Up for unit testing

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
fileopenmockmonkeypatchingbuiltintestingforandunit

Problem

For a specific test-scenario I wanted:

  • avoid accessing the file system through pythons builtin open-function



  • 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

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


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

Code Snippets

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()

Context

StackExchange Code Review Q#9228, answer score: 4

Revisions (0)

No revisions yet.