Python unittest mock: Is it possible to mock the value of a method's default arguments at test time?

前端 未结 3 1505
梦毁少年i
梦毁少年i 2020-12-20 12:09

I have a method that accepts default arguments:

def build_url(endpoint, host=settings.DEFAULT_HOST):
    return \'{}{}\'.format(host, endpoint)
相关标签:
3条回答
  • 2020-12-20 12:41

    I applied the other answer to this question, but after the context manager, the patched function was not the same as before.

    My patched function looks like this:

    def f(foo=True):
        pass
    

    In my test, I did this:

    with patch.object(f, 'func_defaults', (False,)):
    

    When calling f after (not in) the context manager, the default was completely gone rather than going back to the previous value. Calling f without arguments gave the error TypeError: f() takes exactly 1 argument (0 given)

    Instead, I just did this before my test:

    f.func_defaults = (False,)
    

    And this after my test:

    f.func_defaults = (True,)
    
    0 讨论(0)
  • 2020-12-20 12:42

    Functions store their parameter default values in the func_defaults attribute when the function is defined, so you can patch that. Something like

    def test_build_url(self):
        """ If only endpoint is supplied should default to settings"""
    
        # Use `func_defaults` in Python2.x and `__defaults__` in Python3.x.
        with patch.object(build_url, 'func_defaults', ('domain',)):
          result = build_url('/end')
          expected = 'domain/end'
    
        self.assertEqual(result,expected)
    

    I use patch.object as a context manager rather than a decorator to avoid the unnecessary patch object being passed as an argument to test_build_url.

    0 讨论(0)
  • 2020-12-20 12:43

    An alternate way to do this: Use functools.partial to provide the "default" args you want. This isn't technically the same thing as overriding them; the call-ee sees an explicit arg, but the call-er doesn't have to provide it. That's close enough most of the time, and it does the Right Thing after the context manager exits:

    # mymodule.py
    def myfunction(arg=17):
        return arg
    
    # test_mymodule.py
    from functools import partial
    from mock import patch
    
    import mymodule
    
    class TestMyModule(TestCase):
        def test_myfunc(self):
            patched = partial(mymodule.myfunction, arg=23)
            with patch('mymodule.myfunction', patched):
                self.assertEqual(23, mymodule.myfunction())  # Passes; default overridden
            self.assertEqual(17, mymodule.myfunction()) # Also passes; original default restored
    

    I use this for overriding default config file locations when testing. Credit where due, I got the idea from Danilo Bargen here

    0 讨论(0)
提交回复
热议问题