2

Following the approach in this answer I am able to override the default values of a function using the approach below, see test_foo_defaults.

main.py:

def bar(x):
    return x * 2

def foo(a, b=bar, c=4):
    return f'{a} {b(a)} {c}'

main_test.py

import unittest
from unittest import mock
from unittest.mock import MagicMock
import main

def test_foo():
    assert '3 6 4' == main.foo(3)

def test_foo_defaults():
    m = MagicMock()
    m.return_value = 12
    with mock.patch.object(main.foo, '__defaults__', (m, 7)):
        assert '3 12 7' == main.foo(3)

@mock.patch.object(main.foo.__defaults__, 0)
def test_foo_defaults_2(m):
    m.return_value = 12
    assert '3 12 5' == main.foo(3)

However, I'd like to be able to do something like test_foo_defaults2, in which I only override one default parameter and get a handle to it as a mock passed to my test function. test_foo_defaults2 does not work unfortunately, but fails with: TypeError: getattr(): attribute name must be string.

Are there any solutions to this?

Daniel Walker
  • 6,380
  • 5
  • 22
  • 45
Paul I
  • 860
  • 2
  • 9
  • 16

3 Answers3

1

Have you tried:

@mock.patch.object(main.foo, '__defaults__', (5,))
def test_foo_defaults():
    assert '3 5' == main.foo(3)

The documentation says unittest.mock.patch.object can be used as a decorator.

Jasmijn
  • 9,370
  • 2
  • 29
  • 43
1

new_callable parameter can be used to replace the function that initializes the injected mock value.

new_callable allows you to specify a different class, or callable object, that will be called to create the new object. By default AsyncMock is used for async functions and MagicMock for the rest.

source: https://docs.python.org/3/library/unittest.mock.html#unittest.mock.patch

def init_mocked_defaults():
    return (MagicMock(return_value=6), 5)


@mock.patch.object(main.foo, "__defaults__", new_callable=init_mocked_defaults)
def test_foo_defaults_2(defaults):
    defaults[0].return_value = 12
    assert '3 12 5' == main.foo(3)

Or with an inline lambda:

@mock.patch.object(
    main.foo, "__defaults__",
    new_callable=lambda: (MagicMock(return_value=6), 5)
)
def test_foo_defaults_2(defaults):
    defaults[0].return_value = 12
    assert '3 12 5' == main.foo(3)

Truth be told both solution looks very disturbing.

Attila Viniczai
  • 644
  • 2
  • 8
1

@Attila Viniczai your answer got me part of the way there. The other intent was to avoid having to specify the second element in __defaults__. This works for me:

def init_mocked_defaults(**kwargs):
    r = list(main.foo.__defaults__)
    r[0] = MagicMock()
    return tuple(r)                                                                                                                                        
 
@mock.patch.object(main.foo, "__defaults__", new_callable=init_mocked_defaults)
def test_foo_defaults_2(defaults):
    defaults[0].return_value = 12
    assert '3 12 4' == main.foo(3)
Paul I
  • 860
  • 2
  • 9
  • 16