16

Is it possible to mock the builtin len() function in Python 3.6?

I have a class which defines a simple method whic is dependent upon the len() function as follows:

class MyLenFunc(object):
    def is_longer_than_three_characters(self, some_string):
        return len(some_string) > 3

I am trying to write a unit test for this method, but I am unable to mock out the len() function without errors being produced. Here's what I have so far:

import unittest
from unittest.mock import patch
import my_len_func


class TestMyLenFunc(unittest.TestCase):

    @patch('builtins.len')
    def test_len_function_is_called(self, mock_len):
        # Arrange
        test_string = 'four'

        # Act
        test_object = my_len_func.MyLenFunc()
        test_object.is_longer_than_three_characters(test_string)

        # Assert
        self.assertEqual(1, mock_len.call_count)


if __name__ == '__main__':
    unittest.main()

I found another SO question/answer here which suggests that it is not possible to mock out builtin functions because they are immutable. However, I found a couple more websites here and here which suggest otherwise. My attempt at the unit test class above is taken directly from the latter of these websites (and yes, I've tried the other techniques mentioned there. All end with the same error).

The error I'm getting is rather long to post the full thing, so I'll snip out the repeating parts of it (you'll see that it's recursive from the final part of the error message). The error text is as follows:

ERROR: test_len_function_is_called (__main__.TestMyLenFunc)
---------------------------------------------------------------------- 
Traceback (most recent call last):
    File "C:\Python36\Lib\unittest\mock.py", line 1179, in patched
        return func(*args, **keywargs)
    File "C:/Python/scratchpad/my_len_func_tests.py", line 16, in test_len_function_is_called
        test_object.is_longer_than_three_characters(test_string)
    File "C:\Python\scratchpad\my_len_func.py", line 3, in is_longer_than_three_characters
        return len(some_string) > 3
    File "C:\Python36\Lib\unittest\mock.py", line 939, in __call__
        return _mock_self._mock_call(*args, **kwargs)
    File "C:\Python36\Lib\unittest\mock.py", line 949, in _mock_call
        _call = _Call((args, kwargs), two=True)
    File "C:\Python36\Lib\unittest\mock.py", line 1972, in __new__
        _len = len(value)
    ...
    (Repeat errors from lines 939, 949, and 1972 another 95 times!)
    ...
    File "C:\Python36\Lib\unittest\mock.py", line 939, in __call__
        return _mock_self._mock_call(*args, **kwargs)
    File "C:\Python36\Lib\unittest\mock.py", line 944, in _mock_call
        self.called = True RecursionError: maximum recursion depth exceeded while calling a Python object

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (errors=1)

Many thanks in advance.

DatHydroGuy
  • 1,056
  • 3
  • 11
  • 18
  • 1
    Couldn't you mock the corresponding `__len__()` method? – Eric Duminil Jul 05 '17 at 08:05
  • The answer you found concerns *methods on built-in **types***. `len()` is not such a method. You can indeed just replace the reference on `builtins` to replace it with a different implementation. You can't do the same for, say, `str.count`. – Martijn Pieters Jul 05 '17 at 08:10
  • @eric-duminil Thank you for the suggestion. I did actually try that, but I got the error `AttributeError: does not have the attribute '__len__'` – DatHydroGuy Jul 05 '17 at 11:46
  • @martijn-pieters Gah! You're right! I'm trying to mock the wrong sort of thing, aren't I? Thank you so much, and for your answer below. – DatHydroGuy Jul 05 '17 at 11:47
  • @DatHydroGuy: Eric meant for you to pass in an object with a mocked `__len__` (because `len(ob)` calls `type(ob).__len__(ob)` for instances of a custom Python class). However, that'd make things needlessly complicated, as you now have to emulate all other operations that `str` offers and your code needs, in a mock object to replace the string you pass in. – Martijn Pieters Jul 05 '17 at 11:49
  • Ah, I see. Apologies for my misunderstanding. It's an interesting idea, but as you say it increases the complexity, especially if I require additional functionality from `str`. – DatHydroGuy Jul 05 '17 at 12:06

1 Answers1

23

Don't patch builtins.len; now code in the mock library breaks, because it needs the function to operate as normal! Patch the globals of the module-under-test:

@patch('my_len_func.len')

This adds the global len to your module, and the len(some_string) in the MyLenFunc().is_longer_than_three_characters() method will find that one rather than the built-in function.

However, I must say that testing if len() is called feels like the wrong thing to test. You want to check if the method is producing the right results for the given inputs, basic operations like len() are just small building blocks towards that goal and are usually not tested that extent.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • 3
    Thank you very much. This approach worked beautifully. I completely agree that testing whether `len()` gets called is wrong. This was just a cut-down example of the actual issue I'm working on, and it just happened to illustrate the problem I was having. – DatHydroGuy Jul 05 '17 at 11:49
  • Good point, but one might want to get the return_value of the len to use it in another step of the test – user3841581 Jan 02 '21 at 13:22