Is it possible to mock the string module from Python?

4k Views Asked by At

For instance, if I have a call to the split method (i.e. some_string.split(":") ) Is is possible to mock this. I wanted to assert that the split function is called using assert_called_once_with

2

There are 2 best solutions below

0
On

Yes it is with a couple of caviats. In my case I have successfully mocked str in python3 so I can assert that split is being called with a specific input

There are two caviats

  • With patch, I replaced the original str class with a new class that inherits from str
  • In the code that I was testing, I had to do a redundant string casting like str(str_val).split

Here's how one can do it:

class MyStr(str):
    def split(self, sep=None, maxsplit=-1)):
        expected_str = "some_input_mutated_inside_fn_before_split_called"
        self.assertEqual(self, expected_str)
        return super().split(sep=sep, maxsplit=maxsplit)

with patch('mymodule.str', new=MyStr):
    output = mymodule.function_that_calls_string_split(
        "some_input"
    )
0
On

I confirm you can't do that because split() is a built-in attribute of str object and you can't set attributes of built-in or extension because they are readonly.

Below some inconclusive tests after trying into a Python 2.7.10 interpreter

>>> __builtins__.str.split
<method 'split' of 'str' objects>
>>> type(__builtins__.str.split)
<type 'method_descriptor'>

Trying to override it using a function

>>> type(lambda f:f)
<type 'function'>
>>> __builtins__.str.split = lambda f: f
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'str'

Trying to override it using a callable (function or method)

>>> type(callable)
<type 'builtin_function_or_method'>
>>> __builtins__.str.split = callable
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't set attributes of built-in/extension type 'str'

After having a look more deeply into the CPython source code here [1]. It's a limitation in Objects/typeobject.c introduce by the function list below. This function check if we try to set a readonly attribute and raise TypeError.

    type_setattro(PyTypeObject *type, PyObject *name, PyObject *value)
{
    if (!(type->tp_flags & Py_TPFLAGS_HEAPTYPE)) {
        PyErr_Format(
            PyExc_TypeError,
            "can't set attributes of built-in/extension type '%s'",
            type->tp_name);
        return -1;
    }
    if (PyObject_GenericSetAttr((PyObject *)type, name, value) < 0)
        return -1;
    return update_slot(type, name);
}

[1] https://hg.python.org/cpython/file/tip/Objects/typeobject.c#l3022