Pytest:使用字典参数断言对函数的多次调用

Pytest: Asserting multiple calls on a function with a dictionary parameter

我想测试一个 python 片段,它从分页的 API 请求信息并在有更多信息可用时更新偏移量。

一些最小的示例代码:

def fetch(url, params):
    # call the API

def fetch_all():
    params = {"offset": 0}
    while True:
        result = fetch("example.com", params).json()
        if len(result) > 1000: # If there are more items
            params["offset"] += 1
        else:
            break

在测试此函数时,我正在模拟 fetch 函数并检查它是否有多个具有不同偏移量的调用。不幸的是,所有这些调用似乎都引用了同一个字典。 因此,如果我遍历它,例如3次,看起来像这样:

预计:

call("example.com", {"offset": 0}),
call("example.com", {"offset": 1}),
call("example.com", {"offset": 2})

实际:

call("example.com", {"offset": 2}),
call("example.com", {"offset": 2}),
call("example.com", {"offset": 2})

如果我改为将代码更改为 result = fetch("example.com", params.copy()),它会正常工作。所以模拟中记录的调用肯定似乎引用了同一个字典。

我该如何解决?我真的不想总是为了能够对其进行一些测试而交出副本。

-- 编辑 --

我整理了一个工作示例(称为 test.py)来显示所述行为:

def fetch(url: str, params: dict):
    pass # This is getting mocked

def fetch_all():
    params = {"offset": 0}
    result = []

    while True:
        data = fetch("https://example.com", params)
        result.extend(data)

        if len(data) >= 5:
            params["offset"] += 1
        else:
            break
    return result


def test_fetch_all(mocker):
    side_effect = [
        [1,2,3,4,5],
        [6,7]
    ]

    mocked_fetch = mocker.patch("test.fetch", side_effect=side_effect)

    assert fetch_all() == [1,2,3,4,5,6,7]

    mocked_fetch.assert_has_calls([
        mocker.call("https://example.com", params={"offset": 0}),
        mocker.call("https://example.com", params={"offset": 1})
    ])

导致:

AssertionError: Calls not found.

Expected: [call('https://example.com', params={'offset': 0}),
           call('https://example.com', params={'offset': 1})]

 Actual: [call('https://example.com', {'offset': 1}),
          call('https://example.com', {'offset': 1})]

您可以创建存根方法并在每次调用该方法时存储参数的副本,然后针对您存储的副本进行断言。

我对您的工作示例进行了一些修改以使其工作:

def test_fetch_all(mocker):
    return_values = [
        [1, 2, 3, 4, 5],
        [6, 7]
    ]
    call_parameters = []

    def my_fetch(url: str, params: dict):
        call_parameters.append((url, params.copy()))
        return return_values.pop(0)

    mocker.patch("test.fetch", side_effect=my_fetch)

    assert fetch_all() == [1, 2, 3, 4, 5, 6, 7]

    assert call_parameters == [
        ("https://example.com", {"offset": 0}),
        ("https://example.com", {"offset": 1})
    ]