Mock default parameter value in inner function

354 views Asked by At

I have the following (simplified) piece of code:

def get_redis()
  return redis_instance


def bar(key, value, redis=get_redis())
  redis.set(key, value)


def foo()
  bar("key", value)

In my test I want to mock the function get_redis to return an instance of fakeredis.FakeStrictRedis(), so I did this

def test_foo(mocker):
  mocker.patch("app.main.get_redis", return_value=fakeredis.FakeStrictRedis())
  foo()

the mocked function has no effect, the foo function try to connect to real redis using the get_redis function from main.

If I wrote in this way works

def bar(key, value)
  redis=get_redis()
  redis.set(key, value)

This works, but I can pass redis as default value. How can I mock?

2

There are 2 answers

1
2ps On BEST ANSWER

I would just modify the bar function slightly as follows so that your functions don't get called before the mock can be applied:

def bar(key, value, redis=get_redis)
    if callable(redis):
        redis = redis()
    redis.set(key, value)

Writing the function this way means that your mock will apply at the time the function is called, and not at startup before any mocks can be applied. In short, writing bar this way, ensures the return of get_redis it will propagate through your bar function every time the function is called .

0
mbj On

There's nothing wrong with your mock, but you seem to be misunderstanding how default parameter values work. As explained in the Python Language Reference, default parameter values are evaluated when the function definition is executed.

In your case, this means that the original get_redis is called already when bar is defined:

def bar(key, value, redis=get_redis()):

This statement is executed when pytest imports your module, i.e., before test_foo is executed, so mocking get_redis in the test has no effect because it's already too late by then.

To make the default-supplying factory function mockable, use None as the default and make the function call the factory unless another value was specified in the call:

def bar(key, value, redis=None)
  redis = redis or get_redis()
  redis.set(key, value)