1

Is it possible to mock a function say append from the builtin list class? The following does not seem to work

with patch("my_class.list.append", MagicMock()) as mock_dict:

I don't want to mock the whole class, just a function.

I have no control over the code I am testing, so I cannot wrap the function with one of my own.

If anyone could point me in the right direction or towards some documentation, I will be deeply grateful.

khelwood
  • 55,782
  • 14
  • 81
  • 108
Rahul
  • 177
  • 3
  • 9
  • Interesting case, could you provide some more details? The minimum part that you want to mock, and what the expected result is? – Thymen Nov 29 '20 at 20:01
  • @Thymen I am testing code for a bunch of students and there are certain methods they are not allowed to use. So I thought if I could mock those functions, I could track if those functions get called or not – Rahul Nov 29 '20 at 20:13
  • 1
    Ah right, in that case my answer could help you if they would use a variable that is created from the `MyList` class. So instead of given them `variable = list()`, `variable = MyList()`. Alternatively you could write a regex expression that replaces all the code using `list()` with `MyList()` and insert the `MyList` import on the first line of their file. – Thymen Nov 29 '20 at 20:18

2 Answers2

2

It is not directly possible to change a built in class, without going into a lot of trouble, check this post about the forbiddenfruit repo.

However we can ofcourse inherit from the built-in and then manually overwrite the append method.

Without more detail I am not sure if this minimal working examplee would solve your problem:

class MyList(list):
    def append(self, value):
        print('Mocked')


class Foo:
    var = list()
    var_filled = list([5, 6, 7, 8])


mock_foo_var = Foo.var
mock_foo_var = MyList(*mock_foo_var)
mock_foo_var.append(10)

mock_var_filled = Foo.var_filled
mock_var_filled = MyList(mock_var_filled)
mock_var_filled.append(10)

print(mock_foo_var)
print(mock_var_filled)

This would lead to the output:

Mocked
Mocked
[]
[5, 6, 7, 8]

Note that the value 10 is not added, but used to print the Mocked output instead. You can change this behavior in any way you want.

Extra:

Maybe you do want to append a value, the easiest way to do that is by using insert and len

def append(self, value):
    self.insert(len(self), value)
    print('Mocked')

If this is not what you were looking for, please update the question with more details.


Edit 1

Since we now have a new instance of MyList we can also change the list reference on the module level. You cannot change a method of list, but you are allowed to rename the variable. So the following is also possible:

list = MyList

var = list()
var.append(10)

Which will result in the output:

Mocked

This reference will work if it is located in the same module (before list is called).


Edit 2

While thinking about it for a bit longer, the use case is actually not that simple. There are 3 common ways of creating a list in python:

  1. var = list()
  2. var = []
  3. var = list([])

The above described method will only work for the first and last case.

[] = list()
list = MyList

def test_case_1():
    var = list()
    var.append(10)
    print("Passed test 1:", len(var) == 0)


def test_case_2():
    var = list([])
    var.append(10)
    print("Passed test 2:", len(var) == 0)


def test_case_3():
    var = list([1, 2, 3])
    var.append(10)
    print("Passed test 3:", len(var) == 3)


def test_case_4():
    var = []
    var.append(10)
    print("Passed test 4:", len(var) == 0)


def test_case_5():
    var = [1, 2, 3]
    var.append(10)
    print("Passed test 5:", len(var) == 3)

test_case_1()
test_case_2()
test_case_3()
test_case_4()
test_case_5()

Will result in the output:

Mocked
Passed test 1: True
Mocked
Passed test 2: True
Mocked
Passed test 3: True
Passed test 4: False
Passed test 5: False

The var for test case 4 and 5 will contain:

  1. [10]
  2. [1, 2, 3, 10]

So when using this solution all lists have to be explicitly created using list(), and cannot use the [ ] list assignment.

If anybody knows how to override the [ ] function, I would be interested to know how.

Thymen
  • 2,089
  • 1
  • 9
  • 13
0

I recently released a python library called fishhook that allows for hooking of methods in a slightly different way than forbiddenfruit that makes it a tad more stable while also providing a way to call the original implementation.

for list.append you would use it as such

from fishhook import hook, unhook, orig

@hook(list)
def append(self, item):
     print('list.append was called')
     return orig(self, item)

to unhook the method, call unhook(list, 'append')