[{"data":1,"prerenderedAt":1235},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fassert-called-with-vs-call-args-list\u002F":3},{"id":4,"title":5,"body":6,"description":1199,"extension":1200,"meta":1201,"navigation":99,"path":1231,"seo":1232,"stem":1233,"__hash__":1234},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fassert-called-with-vs-call-args-list\u002Findex.md","assert_called_with vs call_args_list in Python Mocks",{"type":7,"value":8,"toc":1188},"minimark",[9,33,38,70,74,77,269,279,388,392,440,557,561,568,639,656,659,679,682,686,689,703,737,743,747,758,764,783,796,830,833,969,973,1068,1072,1087,1098,1119,1146,1150,1178,1184],[10,11,12,13,17,18,21,22,25,26,29,30,32],"p",{},"A test asserts ",[14,15,16],"code",{},"client.post.assert_called_with(url, json=payload)"," and passes. The code under test called ",[14,19,20],{},"client.post"," four times: three with a malformed payload, and once — last — correctly. The assertion never had a chance to catch it, because ",[14,23,24],{},"assert_called_with"," compares against ",[14,27,28],{},"call_args",", and ",[14,31,28],{}," holds the most recent call only. Choosing the wrong assertion is the most common way a mock-based test proves less than its author believes.",[34,35,37],"h2",{"id":36},"prerequisites","Prerequisites",[39,40,41,57],"ul",{},[42,43,44,48,49,52,53,56],"li",{},[45,46,47],"strong",{},"Python 3.8+"," (",[14,50,51],{},"AsyncMock"," and ",[14,54,55],{},"assert_awaited_*"," need 3.8; everything else is older).",[42,58,59,60,63,64,69],{},"The ",[14,61,62],{},"Mock"," call-recording model from ",[65,66,68],"a",{"href":67},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002F","deep dive into unittest.mock",".",[34,71,73],{"id":72},"solution","Solution",[10,75,76],{},"Every assertion reads one of four recorded fields. Match the assertion to the claim.",[78,79,84],"pre",{"className":80,"code":81,"language":82,"meta":83,"style":83},"language-python shiki shiki-themes github-light github-dark","from unittest.mock import Mock, call, ANY\n\nclient = Mock()\nclient.post(\"\u002Fcharges\", json={\"amount\": 1})\nclient.post(\"\u002Fcharges\", json={\"amount\": 2})\nclient.post(\"\u002Fcharges\", json={\"amount\": 3})\n\n# 1. LAST call only — passes even though two earlier calls were different.\nclient.post.assert_called_with(\"\u002Fcharges\", json={\"amount\": 3})\n\n# 2. Exactly one call, with these arguments. Fails here: call_count is 3.\ntry:\n    client.post.assert_called_once_with(\"\u002Fcharges\", json={\"amount\": 3})\nexcept AssertionError as exc:\n    print(\"count checked:\", exc)\n\n# 3. Somewhere in the history, in any position.\nclient.post.assert_any_call(\"\u002Fcharges\", json={\"amount\": 1})\n\n# 4. This exact sequence, in this order.\nclient.post.assert_has_calls([\n    call(\"\u002Fcharges\", json={\"amount\": 1}),\n    call(\"\u002Fcharges\", json={\"amount\": 2}),\n])\n\n# 5. The raw history, when the assertion is more specific than any helper.\namounts = [c.kwargs[\"json\"][\"amount\"] for c in client.post.call_args_list]\nassert amounts == [1, 2, 3]\n\n# 6. Partial matching: assert the url, ignore the payload entirely.\nclient.post.assert_any_call(\"\u002Fcharges\", json=ANY)\n","python","",[14,85,86,94,101,107,113,119,125,130,136,142,147,153,159,165,171,177,182,188,194,199,205,211,217,223,229,234,240,246,252,257,263],{"__ignoreMap":83},[87,88,91],"span",{"class":89,"line":90},"line",1,[87,92,93],{},"from unittest.mock import Mock, call, ANY\n",[87,95,97],{"class":89,"line":96},2,[87,98,100],{"emptyLinePlaceholder":99},true,"\n",[87,102,104],{"class":89,"line":103},3,[87,105,106],{},"client = Mock()\n",[87,108,110],{"class":89,"line":109},4,[87,111,112],{},"client.post(\"\u002Fcharges\", json={\"amount\": 1})\n",[87,114,116],{"class":89,"line":115},5,[87,117,118],{},"client.post(\"\u002Fcharges\", json={\"amount\": 2})\n",[87,120,122],{"class":89,"line":121},6,[87,123,124],{},"client.post(\"\u002Fcharges\", json={\"amount\": 3})\n",[87,126,128],{"class":89,"line":127},7,[87,129,100],{"emptyLinePlaceholder":99},[87,131,133],{"class":89,"line":132},8,[87,134,135],{},"# 1. LAST call only — passes even though two earlier calls were different.\n",[87,137,139],{"class":89,"line":138},9,[87,140,141],{},"client.post.assert_called_with(\"\u002Fcharges\", json={\"amount\": 3})\n",[87,143,145],{"class":89,"line":144},10,[87,146,100],{"emptyLinePlaceholder":99},[87,148,150],{"class":89,"line":149},11,[87,151,152],{},"# 2. Exactly one call, with these arguments. Fails here: call_count is 3.\n",[87,154,156],{"class":89,"line":155},12,[87,157,158],{},"try:\n",[87,160,162],{"class":89,"line":161},13,[87,163,164],{},"    client.post.assert_called_once_with(\"\u002Fcharges\", json={\"amount\": 3})\n",[87,166,168],{"class":89,"line":167},14,[87,169,170],{},"except AssertionError as exc:\n",[87,172,174],{"class":89,"line":173},15,[87,175,176],{},"    print(\"count checked:\", exc)\n",[87,178,180],{"class":89,"line":179},16,[87,181,100],{"emptyLinePlaceholder":99},[87,183,185],{"class":89,"line":184},17,[87,186,187],{},"# 3. Somewhere in the history, in any position.\n",[87,189,191],{"class":89,"line":190},18,[87,192,193],{},"client.post.assert_any_call(\"\u002Fcharges\", json={\"amount\": 1})\n",[87,195,197],{"class":89,"line":196},19,[87,198,100],{"emptyLinePlaceholder":99},[87,200,202],{"class":89,"line":201},20,[87,203,204],{},"# 4. This exact sequence, in this order.\n",[87,206,208],{"class":89,"line":207},21,[87,209,210],{},"client.post.assert_has_calls([\n",[87,212,214],{"class":89,"line":213},22,[87,215,216],{},"    call(\"\u002Fcharges\", json={\"amount\": 1}),\n",[87,218,220],{"class":89,"line":219},23,[87,221,222],{},"    call(\"\u002Fcharges\", json={\"amount\": 2}),\n",[87,224,226],{"class":89,"line":225},24,[87,227,228],{},"])\n",[87,230,232],{"class":89,"line":231},25,[87,233,100],{"emptyLinePlaceholder":99},[87,235,237],{"class":89,"line":236},26,[87,238,239],{},"# 5. The raw history, when the assertion is more specific than any helper.\n",[87,241,243],{"class":89,"line":242},27,[87,244,245],{},"amounts = [c.kwargs[\"json\"][\"amount\"] for c in client.post.call_args_list]\n",[87,247,249],{"class":89,"line":248},28,[87,250,251],{},"assert amounts == [1, 2, 3]\n",[87,253,255],{"class":89,"line":254},29,[87,256,100],{"emptyLinePlaceholder":99},[87,258,260],{"class":89,"line":259},30,[87,261,262],{},"# 6. Partial matching: assert the url, ignore the payload entirely.\n",[87,264,266],{"class":89,"line":265},31,[87,267,268],{},"client.post.assert_any_call(\"\u002Fcharges\", json=ANY)\n",[10,270,271,272,274,275,278],{},"The distinction that matters most in review is between (1) and (2). ",[14,273,24],{}," makes no claim about how many calls happened; ",[14,276,277],{},"assert_called_once_with"," does. When the intent is \"the code called this once, like so\" — which it usually is — the second form is the honest one, and it costs four characters.",[280,281,284,384],"figure",{"className":282},[283],"diagram",[285,286,293,294,293,298,293,302,293,310,293,319,293,327,293,331,293,335,293,341,293,345,293,347,293,351,293,354,293,358,293,360,293,364,293,367,293,370,293,372,293,376,293,379],"svg",{"viewBox":287,"role":288,"ariaLabelledBy":289,"xmlns":292},"0 0 760 338","img",[290,291],"callfields-t","callfields-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[295,296,297],"title",{"id":290},"What each recorded field holds",[299,300,301],"desc",{"id":291},"A stack of four Mock recording fields: call_args holding only the most recent call, call_args_list holding every call to this mock in order, mock_calls holding calls to children and their results with dotted names, and call_count as the integer number of calls.",[303,304],"rect",{"x":305,"y":305,"width":306,"height":307,"rx":308,"fill":309},"0","760","338","14","#fffdf8",[311,312,297],"text",{"x":313,"y":314,"textAnchor":315,"fontSize":316,"fontWeight":317,"fill":318},"380","30","middle","15","700","#3d405b",[303,320],{"x":314,"y":321,"width":317,"height":322,"rx":323,"fill":324,"stroke":325,"strokeWidth":326},"56","52","10","#f4f1de","#e07a5f","1.8",[303,328],{"x":314,"y":321,"width":329,"height":322,"rx":330,"fill":325},"8","4",[311,332,28],{"x":322,"y":333,"fontSize":334,"fontWeight":317,"fill":318},"86","12.5",[311,336,340],{"x":337,"y":333,"textAnchor":338,"fontSize":339,"fill":318},"714","end","11","the most recent call — overwritten every time",[303,342],{"x":314,"y":343,"width":317,"height":322,"rx":323,"fill":309,"stroke":344,"strokeWidth":326},"120","#81b29a",[303,346],{"x":314,"y":343,"width":329,"height":322,"rx":330,"fill":344},[311,348,350],{"x":322,"y":349,"fontSize":334,"fontWeight":317,"fill":318},"150","call_args_list",[311,352,353],{"x":337,"y":349,"textAnchor":338,"fontSize":339,"fill":318},"every call to this mock, in order",[303,355],{"x":314,"y":356,"width":317,"height":322,"rx":323,"fill":324,"stroke":357,"strokeWidth":326},"184","#f2cc8f",[303,359],{"x":314,"y":356,"width":329,"height":322,"rx":330,"fill":357},[311,361,363],{"x":322,"y":362,"fontSize":334,"fontWeight":317,"fill":318},"214","mock_calls",[311,365,366],{"x":337,"y":362,"textAnchor":338,"fontSize":339,"fill":318},"calls to children too, as call.child.method(...)",[303,368],{"x":314,"y":369,"width":317,"height":322,"rx":323,"fill":309,"stroke":318,"strokeWidth":326},"248",[303,371],{"x":314,"y":369,"width":329,"height":322,"rx":330,"fill":318},[311,373,375],{"x":322,"y":374,"fontSize":334,"fontWeight":317,"fill":318},"278","call_count",[311,377,378],{"x":337,"y":374,"textAnchor":338,"fontSize":339,"fill":318},"how many times this mock itself was called",[311,380,383],{"x":313,"y":381,"textAnchor":315,"fontSize":339,"fontStyle":382,"fill":318},"330","italic","reset_mock() clears all four and leaves return_value and side_effect intact.",[385,386,387],"figcaption",{},"Every assertion helper is a thin wrapper over one of these four; reading them directly is always an option when no helper fits.",[34,389,391],{"id":390},"why-this-works","Why this works",[10,393,394,397,398,401,402,404,405,407,408,410,411,413,414,416,417,419,420,423,424,419,426,429,430,432,433,436,437,439],{},[14,395,396],{},"Mock.__call__"," appends a ",[14,399,400],{},"Call"," object to ",[14,403,350],{},", sets ",[14,406,28],{}," to that same object, increments ",[14,409,375],{},", and propagates an entry into the parent's ",[14,412,363],{}," with the child's name attached. ",[14,415,24],{}," compares its arguments to ",[14,418,28],{},"; ",[14,421,422],{},"assert_any_call"," scans ",[14,425,350],{},[14,427,428],{},"assert_has_calls"," looks for the expected sequence as a contiguous run inside ",[14,431,363],{},". The differences between the assertions are therefore differences in which of those structures they read — not in strictness of comparison, which is identical ",[14,434,435],{},"=="," on ",[14,438,400],{}," objects throughout.",[280,441,443,554],{"className":442},[283],[285,444,293,449,293,452,293,455,293,458,293,460,293,466,293,472,293,476,293,480,293,484,293,489,293,492,293,495,293,498,293,502,293,505,293,508,293,511,293,515,293,518,293,521,293,524,293,528,293,531,293,534,293,537,293,541,293,544,293,547,293,551],{"viewBox":445,"role":288,"ariaLabelledBy":446,"xmlns":292},"0 0 760 310",[447,448],"callassert-t","callassert-d",[295,450,451],{"id":447},"Which assertion makes which claim",[299,453,454],{"id":448},"A table of five call assertions - assert_called, assert_called_with, assert_called_once_with, assert_any_call and assert_has_calls - stating what each checks and what it deliberately does not check.",[303,456],{"x":305,"y":305,"width":306,"height":457,"rx":308,"fill":309},"310",[311,459,451],{"x":313,"y":314,"textAnchor":315,"fontSize":316,"fontWeight":317,"fill":318},[303,461],{"x":462,"y":322,"width":463,"height":464,"rx":323,"fill":357,"stroke":318,"strokeWidth":465},"24","712","40","1.5",[311,467,471],{"x":468,"y":469,"fontSize":470,"fontWeight":317,"fill":318},"38","76","12","Criterion",[311,473,475],{"x":474,"y":469,"textAnchor":315,"fontSize":470,"fontWeight":317,"fill":318},"390","Checks",[311,477,479],{"x":478,"y":469,"textAnchor":315,"fontSize":470,"fontWeight":317,"fill":318},"620","Does not check",[303,481],{"x":462,"y":482,"width":463,"height":464,"fill":324,"stroke":318,"strokeWidth":483},"92","1",[311,485,488],{"x":468,"y":486,"fontSize":487,"fill":318},"116","11.5","assert_called()",[311,490,491],{"x":474,"y":486,"textAnchor":315,"fontSize":487,"fill":318},"at least one call",[311,493,494],{"x":478,"y":486,"textAnchor":315,"fontSize":487,"fill":318},"arguments",[303,496],{"x":462,"y":497,"width":463,"height":464,"fill":309,"stroke":318,"strokeWidth":483},"132",[311,499,501],{"x":468,"y":500,"fontSize":487,"fill":318},"156","assert_called_with(...)",[311,503,504],{"x":474,"y":500,"textAnchor":315,"fontSize":487,"fill":318},"the last call args",[311,506,507],{"x":478,"y":500,"textAnchor":315,"fontSize":487,"fill":318},"call count or history",[303,509],{"x":462,"y":510,"width":463,"height":464,"fill":324,"stroke":318,"strokeWidth":483},"172",[311,512,514],{"x":468,"y":513,"fontSize":487,"fill":318},"196","assert_called_once_with(...)",[311,516,517],{"x":474,"y":513,"textAnchor":315,"fontSize":487,"fill":318},"count is 1 and args",[311,519,520],{"x":478,"y":513,"textAnchor":315,"fontSize":487,"fill":318},"nothing else",[303,522],{"x":462,"y":523,"width":463,"height":464,"fill":309,"stroke":318,"strokeWidth":483},"212",[311,525,527],{"x":468,"y":526,"fontSize":487,"fill":318},"236","assert_any_call(...)",[311,529,530],{"x":474,"y":526,"textAnchor":315,"fontSize":487,"fill":318},"a match exists",[311,532,533],{"x":478,"y":526,"textAnchor":315,"fontSize":487,"fill":318},"position or count",[303,535],{"x":462,"y":536,"width":463,"height":464,"fill":324,"stroke":318,"strokeWidth":483},"252",[311,538,540],{"x":468,"y":539,"fontSize":487,"fill":318},"276","assert_has_calls([...])",[311,542,543],{"x":474,"y":539,"textAnchor":315,"fontSize":487,"fill":318},"ordered subsequence",[311,545,546],{"x":478,"y":539,"textAnchor":315,"fontSize":487,"fill":318},"that nothing else was called",[89,548],{"x1":549,"y1":322,"x2":549,"y2":550,"stroke":318,"strokeWidth":483},"274","292",[89,552],{"x1":553,"y1":322,"x2":553,"y2":550,"stroke":318,"strokeWidth":483},"505",[385,555,556],{},"The right-hand column is where tests go wrong: each helper is silent about something, and that silence is where bugs survive.",[34,558,560],{"id":559},"asserting-across-an-object-graph","Asserting across an object graph",[10,562,563,564,567],{},"When the code under test walks a chain — ",[14,565,566],{},"client.session.get(...)"," — the assertion target is easy to get wrong, because each attribute access creates a distinct child mock with its own history.",[78,569,571],{"className":80,"code":570,"language":82,"meta":83,"style":83},"from unittest.mock import Mock, call\n\nclient = Mock()\nclient.session.get(\"\u002Fa\")\nclient.session.post(\"\u002Fb\", json={})\n\n# The child's own history: only the get.\nassert client.session.get.call_args_list == [call(\"\u002Fa\")]\n\n# The parent's mock_calls: both, with dotted names, in global order.\nassert client.mock_calls == [\n    call.session.get(\"\u002Fa\"),\n    call.session.post(\"\u002Fb\", json={}),\n]\n",[14,572,573,578,582,586,591,596,600,605,610,614,619,624,629,634],{"__ignoreMap":83},[87,574,575],{"class":89,"line":90},[87,576,577],{},"from unittest.mock import Mock, call\n",[87,579,580],{"class":89,"line":96},[87,581,100],{"emptyLinePlaceholder":99},[87,583,584],{"class":89,"line":103},[87,585,106],{},[87,587,588],{"class":89,"line":109},[87,589,590],{},"client.session.get(\"\u002Fa\")\n",[87,592,593],{"class":89,"line":115},[87,594,595],{},"client.session.post(\"\u002Fb\", json={})\n",[87,597,598],{"class":89,"line":121},[87,599,100],{"emptyLinePlaceholder":99},[87,601,602],{"class":89,"line":127},[87,603,604],{},"# The child's own history: only the get.\n",[87,606,607],{"class":89,"line":132},[87,608,609],{},"assert client.session.get.call_args_list == [call(\"\u002Fa\")]\n",[87,611,612],{"class":89,"line":138},[87,613,100],{"emptyLinePlaceholder":99},[87,615,616],{"class":89,"line":144},[87,617,618],{},"# The parent's mock_calls: both, with dotted names, in global order.\n",[87,620,621],{"class":89,"line":149},[87,622,623],{},"assert client.mock_calls == [\n",[87,625,626],{"class":89,"line":155},[87,627,628],{},"    call.session.get(\"\u002Fa\"),\n",[87,630,631],{"class":89,"line":161},[87,632,633],{},"    call.session.post(\"\u002Fb\", json={}),\n",[87,635,636],{"class":89,"line":167},[87,637,638],{},"]\n",[10,640,641,643,644,648,649,652,653,69],{},[14,642,363],{}," on the parent is the only structure that preserves ordering ",[645,646,647],"em",{},"between"," different children, which makes it the right tool for \"the code opened the transaction before writing\" style assertions. It is also the noisiest: accessing ",[14,650,651],{},"client.session"," in the test itself does not record anything, but calling a child's return value does, and those entries appear as ",[14,654,655],{},"call.session.get().json()",[10,657,658],{},"For the common case of asserting that a specific interaction happened somewhere in a busy graph, filter the list rather than matching it whole:",[78,660,662],{"className":80,"code":661,"language":82,"meta":83,"style":83},"posts = [c for c in client.mock_calls if c[0] == \"session.post\"]\nassert len(posts) == 1\nassert posts[0].kwargs[\"json\"] == {}\n",[14,663,664,669,674],{"__ignoreMap":83},[87,665,666],{"class":89,"line":90},[87,667,668],{},"posts = [c for c in client.mock_calls if c[0] == \"session.post\"]\n",[87,670,671],{"class":89,"line":96},[87,672,673],{},"assert len(posts) == 1\n",[87,675,676],{"class":89,"line":103},[87,677,678],{},"assert posts[0].kwargs[\"json\"] == {}\n",[10,680,681],{},"That form survives the addition of unrelated calls, which a whole-list equality assertion does not — and a brittle whole-list assertion is one of the main reasons mock-heavy suites break on every refactor.",[34,683,685],{"id":684},"matching-arguments-loosely","Matching arguments loosely",[10,687,688],{},"Exact-match assertions on large payloads are unreadable and fail for irrelevant reasons. Three tools narrow the claim to what matters.",[10,690,691,694,695,698,699,702],{},[14,692,693],{},"unittest.mock.ANY"," compares equal to anything and nests inside structures, so a timestamp or a generated id can be ignored while the rest of the payload is pinned. For a positional wildcard the same object works. When more than a wildcard is needed — \"any string starting with ",[14,696,697],{},"\u002Fcharges","\" — a small custom matcher class with ",[14,700,701],{},"__eq__"," is idiomatic and reads well at the call site:",[78,704,706],{"className":80,"code":705,"language":82,"meta":83,"style":83},"class StartsWith:\n    def __init__(self, prefix): self.prefix = prefix\n    def __eq__(self, other): return isinstance(other, str) and other.startswith(self.prefix)\n    def __repr__(self): return f\"StartsWith({self.prefix!r})\"   # shown in failure output\n\nclient.post.assert_any_call(StartsWith(\"\u002Fcharges\"), json=ANY)\n",[14,707,708,713,718,723,728,732],{"__ignoreMap":83},[87,709,710],{"class":89,"line":90},[87,711,712],{},"class StartsWith:\n",[87,714,715],{"class":89,"line":96},[87,716,717],{},"    def __init__(self, prefix): self.prefix = prefix\n",[87,719,720],{"class":89,"line":103},[87,721,722],{},"    def __eq__(self, other): return isinstance(other, str) and other.startswith(self.prefix)\n",[87,724,725],{"class":89,"line":109},[87,726,727],{},"    def __repr__(self): return f\"StartsWith({self.prefix!r})\"   # shown in failure output\n",[87,729,730],{"class":89,"line":115},[87,731,100],{"emptyLinePlaceholder":99},[87,733,734],{"class":89,"line":121},[87,735,736],{},"client.post.assert_any_call(StartsWith(\"\u002Fcharges\"), json=ANY)\n",[10,738,59,739,742],{},[14,740,741],{},"__repr__"," is not optional in practice: without it the assertion failure prints the object's default representation and the diff becomes unreadable, which defeats the purpose of the matcher.",[34,744,746],{"id":745},"writing-assertions-that-survive-refactoring","Writing assertions that survive refactoring",[10,748,749,750,753,754,757],{},"Interaction assertions are the most refactor-sensitive tests in a suite, because they encode ",[645,751,752],{},"how"," the code works rather than ",[645,755,756],{},"what"," it produces. Three rules keep them from becoming a tax on every change.",[10,759,760,763],{},[45,761,762],{},"Assert the boundary, not the internals."," A test for an order service should assert what reached the payment gateway, not that an internal helper was called with a particular tuple. The gateway call is a contract with another system; the helper call is an implementation detail that will move.",[10,765,766,769,770,773,774,776,777,779,780,782],{},[45,767,768],{},"Prefer one specific assertion to three vague ones."," ",[14,771,772],{},"assert_called_once_with(url, json=expected)"," states the whole claim in one line. The same claim spread across ",[14,775,488],{},", a ",[14,778,375],{}," check and a ",[14,781,28],{}," inspection is longer, weaker, and fails in three different places when the code changes.",[10,784,785,769,788,791,792,795],{},[45,786,787],{},"Assert absence deliberately.",[14,789,790],{},"assert_not_called()"," is a real assertion and often the most valuable one in the test: proving that the cache path did ",[645,793,794],{},"not"," hit the database is the entire point of a caching test. Mock-heavy suites tend to be full of positive assertions and empty of negative ones, which is why caching, short-circuiting and guard-clause bugs survive them.",[78,797,799],{"className":80,"code":798,"language":82,"meta":83,"style":83},"def test_cache_hit_skips_the_database(cache, db):\n    cache.set(\"k\", \"v\")\n    service = Service(cache=cache, db=db)\n\n    assert service.get(\"k\") == \"v\"\n    db.query.assert_not_called()        # the actual claim of this test\n",[14,800,801,806,811,816,820,825],{"__ignoreMap":83},[87,802,803],{"class":89,"line":90},[87,804,805],{},"def test_cache_hit_skips_the_database(cache, db):\n",[87,807,808],{"class":89,"line":96},[87,809,810],{},"    cache.set(\"k\", \"v\")\n",[87,812,813],{"class":89,"line":103},[87,814,815],{},"    service = Service(cache=cache, db=db)\n",[87,817,818],{"class":89,"line":109},[87,819,100],{"emptyLinePlaceholder":99},[87,821,822],{"class":89,"line":115},[87,823,824],{},"    assert service.get(\"k\") == \"v\"\n",[87,826,827],{"class":89,"line":121},[87,828,829],{},"    db.query.assert_not_called()        # the actual claim of this test\n",[10,831,832],{},"A fourth habit is worth adopting at review time: read every interaction assertion and ask what bug it would catch. If the answer is \"a refactor\", the assertion is measuring structure rather than behaviour, and the test will be deleted the first time it gets in someone's way.",[280,834,836,966],{"className":835},[283],[285,837,293,842,293,845,293,848,293,880,293,883,293,885,293,889,293,893,293,897,293,902,293,907,293,912,293,915,293,919,293,923,293,927,293,931,293,934,293,936,293,939,293,942,293,946,293,951,293,954,293,956,293,959,293,962],{"viewBox":838,"role":288,"ariaLabelledBy":839,"xmlns":292},"0 0 760 332",[840,841],"callpick-t","callpick-d",[295,843,844],{"id":840},"Choosing an assertion from the claim",[299,846,847],{"id":841},"A decision diagram choosing a call assertion by the claim being made: exactly one call with these arguments, a call somewhere in the history, an ordered sequence of calls, or no call at all.",[849,850,851,852,851,865,851,870,851,875,293],"defs",{},"\n    ",[853,854,861],"marker",{"id":855,"viewBox":856,"refX":857,"refY":858,"markerWidth":859,"markerHeight":859,"orient":860},"callpick-a","0 0 10 10","9","5","7","auto-start-reverse",[862,863],"path",{"d":864,"fill":318},"M0 0 L10 5 L0 10 z",[853,866,868],{"id":867,"viewBox":856,"refX":857,"refY":858,"markerWidth":859,"markerHeight":859,"orient":860},"callpick-c",[862,869],{"d":864,"fill":325},[853,871,873],{"id":872,"viewBox":856,"refX":857,"refY":858,"markerWidth":859,"markerHeight":859,"orient":860},"callpick-s",[862,874],{"d":864,"fill":344},[853,876,878],{"id":877,"viewBox":856,"refX":857,"refY":858,"markerWidth":859,"markerHeight":859,"orient":860},"callpick-g",[862,879],{"d":864,"fill":357},[303,881],{"x":305,"y":305,"width":306,"height":882,"rx":308,"fill":309},"332",[311,884,844],{"x":313,"y":314,"textAnchor":315,"fontSize":316,"fontWeight":317,"fill":318},[886,887],"polygon",{"points":888,"fill":324,"stroke":318,"strokeWidth":326},"380,50 590,108 380,166 170,108",[311,890,892],{"x":313,"y":891,"textAnchor":315,"fontSize":470,"fontWeight":317,"fill":318},"104","What exactly are you",[311,894,896],{"x":313,"y":895,"textAnchor":315,"fontSize":470,"fontWeight":317,"fill":318},"121","claiming happened?",[862,898],{"d":899,"fill":900,"stroke":344,"strokeWidth":326,"markerEnd":901},"M380 166 L380 192 L133 192 L133 206","none","url(#callpick-s)",[311,903,906],{"x":904,"y":513,"textAnchor":315,"fontSize":339,"fontWeight":317,"fill":905},"133","#2a5f49","once, like this",[303,908],{"x":462,"y":362,"width":909,"height":910,"rx":470,"fill":309,"stroke":344,"strokeWidth":911},"217","84","2",[311,913,277],{"x":904,"y":914,"textAnchor":315,"fontSize":334,"fontWeight":317,"fill":318},"245",[311,916,918],{"x":904,"y":917,"textAnchor":315,"fontSize":339,"fill":318},"262","count and args together",[311,920,922],{"x":904,"y":921,"textAnchor":315,"fontSize":339,"fill":318},"277","the honest default",[862,924],{"d":925,"fill":900,"stroke":357,"strokeWidth":326,"markerEnd":926},"M380 166 L380 192 L380 192 L380 206","url(#callpick-g)",[311,928,930],{"x":313,"y":513,"textAnchor":315,"fontSize":339,"fontWeight":317,"fill":929},"#8a5a00","at some point",[303,932],{"x":933,"y":362,"width":909,"height":910,"rx":470,"fill":309,"stroke":357,"strokeWidth":911},"271",[311,935,422],{"x":313,"y":914,"textAnchor":315,"fontSize":334,"fontWeight":317,"fill":318},[311,937,938],{"x":313,"y":917,"textAnchor":315,"fontSize":339,"fill":318},"position-independent",[311,940,941],{"x":313,"y":921,"textAnchor":315,"fontSize":339,"fill":318},"busy graphs",[862,943],{"d":944,"fill":900,"stroke":325,"strokeWidth":326,"markerEnd":945},"M380 166 L380 192 L627 192 L627 206","url(#callpick-c)",[311,947,950],{"x":948,"y":513,"textAnchor":315,"fontSize":339,"fontWeight":317,"fill":949},"627","#8f3d22","in this order",[303,952],{"x":953,"y":362,"width":909,"height":910,"rx":470,"fill":309,"stroke":325,"strokeWidth":911},"519",[311,955,428],{"x":948,"y":914,"textAnchor":315,"fontSize":334,"fontWeight":317,"fill":318},[311,957,958],{"x":948,"y":917,"textAnchor":315,"fontSize":339,"fill":318},"contiguous subsequence",[311,960,961],{"x":948,"y":921,"textAnchor":315,"fontSize":339,"fill":318},"protocol order",[311,963,965],{"x":313,"y":964,"textAnchor":315,"fontSize":339,"fontStyle":382,"fill":318},"320","assert_not_called is the fourth, and the one most tests are missing.",[385,967,968],{},"Say the claim out loud first: the helper that matches the sentence is almost always the right one.",[34,970,972],{"id":971},"edge-cases-and-failure-modes","Edge cases and failure modes",[39,974,975,987,1010,1021,1037,1056],{},[42,976,977,982,983,986],{},[45,978,979,981],{},[14,980,24],{}," on a never-called mock"," raises with a clear message, but ",[14,984,985],{},"assert_not_called"," is the explicit way to state that claim.",[42,988,989,769,994,997,998,1001,1002,1005,1006,69],{},[45,990,991,992,69],{},"Typos in assertion names pass silently on a bare ",[14,993,62],{},[14,995,996],{},"mock.assert_called_ones_with(...)"," creates a child mock and calls it, asserting nothing. ",[14,999,1000],{},"create_autospec"," or ",[14,1003,1004],{},"Mock(spec=...)"," prevents this entirely — see ",[65,1007,1009],{"href":1008},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002F","autospec and strict mocking",[42,1011,1012,1017,1018,1020],{},[45,1013,1014,1016],{},[14,1015,428],{}," matches a contiguous run"," within ",[14,1019,363],{},"; interleaved unrelated calls make it fail even when the order is correct. Filter the list first when other calls are expected.",[42,1022,1023,769,1026,52,1029,1032,1033,1036],{},[45,1024,1025],{},"Keyword and positional forms are not interchangeable.",[14,1027,1028],{},"f(1)",[14,1030,1031],{},"f(x=1)"," record differently and compare unequal, unless the mock was built with ",[14,1034,1035],{},"autospec",", which normalises them against the real signature.",[42,1038,1039,1048,1049,52,1052,1055],{},[45,1040,1041,52,1044,1047],{},[14,1042,1043],{},"call_args.args",[14,1045,1046],{},"call_args.kwargs"," need Python 3.8+","; older code indexes ",[14,1050,1051],{},"call_args[0]",[14,1053,1054],{},"call_args[1]",", which still works and is less readable.",[42,1057,1058,769,1061,52,1064,1067],{},[45,1059,1060],{},"Async mocks have parallel assertions.",[14,1062,1063],{},"assert_awaited_with",[14,1065,1066],{},"await_args_list"," mirror the sync versions and are the ones that actually prove the coroutine was awaited rather than merely called.",[34,1069,1071],{"id":1070},"frequently-asked-questions","Frequently Asked Questions",[10,1073,1074,1077,1078,1080,1081,1083,1084,1086],{},[45,1075,1076],{},"Does assert_called_with check every call?","\nNo. It checks only the most recent call, because it compares against ",[14,1079,28],{},", which ",[14,1082,62],{}," overwrites on each call. A test that passes with ",[14,1085,24],{}," may still have made several earlier calls with wrong arguments.",[10,1088,1089,1092,1094,1095,1097],{},[45,1090,1091],{},"What is the difference between assert_has_calls and assert_any_call?",[14,1093,422],{}," checks that one matching call exists anywhere in the history. ",[14,1096,428],{}," checks that a sequence of calls appears in order, contiguously by default within the recorded list, which makes it the stricter of the two.",[10,1099,1100,1103,1104,1106,1107,1110,1111,1114,1115,1118],{},[45,1101,1102],{},"Why does mock_calls contain entries my mock never received?","\nBecause ",[14,1105,363],{}," records calls to child mocks and their return values too, using dotted names. A call to ",[14,1108,1109],{},"mock.session.get()"," appears in ",[14,1112,1113],{},"mock.mock_calls"," as ",[14,1116,1117],{},"call.session.get()",", which is what makes it useful for asserting interaction order across an object graph.",[10,1120,1121,1124,1125,1127,1128,1130,1131,1134,1135,1138,1139,1141,1142,1145],{},[45,1122,1123],{},"How do I assert a call was made with only some arguments matching?","\nUse ",[14,1126,693],{}," for the arguments you do not care about, or inspect ",[14,1129,1046],{}," directly and assert on the individual keys. ",[14,1132,1133],{},"ANY"," compares equal to anything and works inside nested structures.\n",[45,1136,1137],{},"Why does assert_called_once_with pass when the mock was called twice?","\nIt does not — but ",[14,1140,24],{}," does, and the two are easy to confuse at a glance. If a test that should be counting calls is passing, check which of the two names it actually uses; the difference is ",[14,1143,1144],{},"_once"," and it is the difference between checking the call count and ignoring it entirely.",[34,1147,1149],{"id":1148},"related","Related",[39,1151,1152,1158,1164,1171],{},[42,1153,1154,1157],{},[65,1155,1156],{"href":67},"Deep dive into unittest.mock"," — the recording model these assertions read.",[42,1159,1160,1163],{},[65,1161,1162],{"href":1008},"Autospec and strict mocking"," — the only reliable defence against a misspelled assertion that silently passes.",[42,1165,1166,1170],{},[65,1167,1169],{"href":1168},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fmock-vs-magicmock-vs-asyncmock-when-to-use-each\u002F","Mock vs MagicMock vs AsyncMock — when to use each"," — the await-aware assertions on the async side.",[42,1172,1173,1177],{},[65,1174,1176],{"href":1175},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fautospec-strict-mocking\u002Fresolving-side_effect-and-return_value-conflicts\u002F","Resolving side_effect and return_value conflicts"," — why a mock returned something the assertions did not expect.",[10,1179,1180,1181],{},"← Back to ",[65,1182,1183],{"href":67},"Deep Dive into unittest.mock",[1185,1186,1187],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":83,"searchDepth":96,"depth":96,"links":1189},[1190,1191,1192,1193,1194,1195,1196,1197,1198],{"id":36,"depth":96,"text":37},{"id":72,"depth":96,"text":73},{"id":390,"depth":96,"text":391},{"id":559,"depth":96,"text":560},{"id":684,"depth":96,"text":685},{"id":745,"depth":96,"text":746},{"id":971,"depth":96,"text":972},{"id":1070,"depth":96,"text":1071},{"id":1148,"depth":96,"text":1149},"Know which unittest.mock assertion checks which calls: assert_called_with sees only the last call, while call_args_list, assert_any_call and assert_has_calls see the history.","md",{"slug":1202,"type":1203,"breadcrumb":1204,"datePublished":1205,"dateModified":1205,"faq":1206,"howto":1215},"assert-called-with-vs-call-args-list","article","Call Assertions","2026-08-01",[1207,1209,1211,1213],{"q":1076,"a":1208},"No. It checks only the most recent call, because it compares against call_args, which Mock overwrites on each call. A test that passes with assert_called_with may still have made several earlier calls with wrong arguments.",{"q":1091,"a":1210},"assert_any_call checks that one matching call exists anywhere in the history. assert_has_calls checks that a sequence of calls appears in order, contiguously by default within the recorded list, which makes it the stricter of the two.",{"q":1102,"a":1212},"Because mock_calls records calls to child mocks and their return values too, using dotted names. A call to mock.session.get() appears in mock.mock_calls as call.session.get(), which is what makes it useful for asserting interaction order across an object graph.",{"q":1123,"a":1214},"Use unittest.mock.ANY for the arguments you do not care about, or inspect call_args.kwargs directly and assert on the individual keys. ANY compares equal to anything and works inside nested structures.",{"name":1216,"description":1217,"steps":1218},"How to assert on mock calls correctly","Pick the assertion that matches the claim you are making about the call history.",[1219,1222,1225,1228],{"name":1220,"text":1221},"Decide whether the claim is about one call or the history","assert_called_with speaks about the last call only; the list-based assertions speak about all of them.",{"name":1223,"text":1224},"Use assert_called_once_with for exactly-once claims","It fails when the call count is not one, which assert_called_with does not check.",{"name":1226,"text":1227},"Use assert_has_calls for ordering","Pass the expected sequence of call objects to assert the order they were made in.",{"name":1229,"text":1230},"Inspect call_args_list when the assertion is complex","Read the recorded calls directly and assert on the fields that matter.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fassert-called-with-vs-call-args-list",{"title":5,"description":1199},"advanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fassert-called-with-vs-call-args-list\u002Findex","ku4V7mXYT-APFd4Sx5ylrUggp5YfsgqwTekF4nQe8HY",1785613403107]