So this involves a maybe unusual chain of things:
A.py
from B import call
def make_call():
print("I'm making a call!")
call(phone_number="867-5309")
B.py
def call(phone_number):
pass
test_A.py
import pytest
from A import make_call
@pytest.fixture
def patch_A_call(monkeypatch):
number = "NOT ENTERED"
number_list = []
def call(phone_number):
nonlocal number
number = phone_number
number_list.append(phone_number)
monkeypatch.setattr("A.call", call)
return (number, number_list)
def test_make_call(patch_A_call):
make_call()
print(f"number: {patch_A_call[0]}")
print(f"number_list: {patch_A_call[1]}")
What's printed is:
number: NOT ENTERED
number_list: [867-5309]
I expected "867-5309" to be the value for both results.
I know that lists are passed by reference in Python—but I assumed that the nonlocal declaration would pass the variable along down the chain.
Why doesn't it work this way?
If you want to see how
callis called, I think a simpler option is to replace it with a Mock object:Here, we're replacing
A.callwith aunittest.mock.Mockobject. This gets called bymake_call, and the call arguments are recorded by the Mock object for later inspection.This requires substantially less code.
Note that I'm using an
assertstatement here, but you could instead print out or otherwise record the value ofphone_numberif that's your goal.The primary problem with your solution is that your
patch_A_callfixture is called once, before yourtest_make_callmethod executes.So while the
nonlocalkeyword is working as intended...you never see the result, because thatreturn (number, number_list)statement ran before you made the call tomake_call.You see the result in the list because a list is a "container" -- you add the number to it when calling
make_call, and you can see the result because the returned list is the same object available from inside your patchedcallmethod.For my solution, we don't have to use
mock.patch(); we can rewrite your fixture like this:This accomplishes pretty much the same thing.