[{"data":1,"prerenderedAt":861},["ShallowReactive",2],{"page-\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Fasserting-await-order-with-asyncmock\u002F":3},{"id":4,"title":5,"body":6,"description":824,"extension":825,"meta":826,"navigation":80,"path":857,"seo":858,"stem":859,"__hash__":860},"content\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Fasserting-await-order-with-asyncmock\u002Findex.md","Asserting Await Order with AsyncMock",{"type":7,"value":8,"toc":814},"minimark",[9,22,25,30,54,58,181,312,316,326,336,343,347,411,415,435,446,558,561,631,635,638,649,652,663,726,729,733,746,766,772,776,805,810],[10,11,12,13,17,18,21],"p",{},"Some async code is correct only if things happen in a particular order: persist the order before publishing the ",[14,15,16],"code",{},"order_created"," event, acquire the lock before reading the balance, commit before acknowledging the message. Each collaborator can be doubled with an ",[14,19,20],{},"AsyncMock",", and each double records its own awaits — but none of them knows about the others, so per-mock assertions cannot tell whether the publish happened before or after the write.",[10,23,24],{},"That gap is not academic. Ordering bugs in async code are some of the hardest to find in production, because they depend on timing and appear only under load, and the tests that should have caught them usually did assert on each collaborator — just never on the relationship between them. A suite can have full coverage of the save path and full coverage of the publish path and still permit publishing before saving. The tool for the global view is a shared parent mock. Attach each child to it, and the parent records every call to every child in the order they occurred. One list comparison then asserts the sequence, and a per-child await check closes the gap where a coroutine was created but never awaited.",[26,27,29],"h2",{"id":28},"prerequisites","Prerequisites",[31,32,33,40,46],"ul",{},[34,35,36,37,39],"li",{},"Python 3.8+ for ",[14,38,20],{},".",[34,41,42,45],{},[14,43,44],{},"pytest >= 8.0"," with an async runner.",[34,47,48,49,39],{},"The async mocking rules from ",[50,51,53],"a",{"href":52},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002F","patching async code and coroutines",[26,55,57],{"id":56},"solution","Solution",[59,60,65],"pre",{"className":61,"code":62,"language":63,"meta":64,"style":64},"language-python shiki shiki-themes github-light github-dark","from unittest.mock import AsyncMock, MagicMock, call\n\n\nasync def test_order_is_persisted_before_the_event_is_published():\n    parent = MagicMock()\n    repo_save = AsyncMock()\n    bus_publish = AsyncMock()\n    # Children record into the parent's mock_calls in real time order.\n    parent.attach_mock(repo_save, \"save\")\n    parent.attach_mock(bus_publish, \"publish\")\n\n    service = OrderService(save=repo_save, publish=bus_publish)\n    await service.place(an_order(id=\"ord_1\"))\n\n    # The sequence is the contract: write, then announce.\n    assert [name for name, _args, _kwargs in parent.mock_calls] == [\"save\", \"publish\"]\n    # And both coroutines were actually awaited, not just created.\n    repo_save.assert_awaited_once()\n    bus_publish.assert_awaited_once_with(\"order_created\", {\"order_id\": \"ord_1\"})\n","python","",[14,66,67,75,82,87,93,99,105,111,117,123,129,134,140,146,151,157,163,169,175],{"__ignoreMap":64},[68,69,72],"span",{"class":70,"line":71},"line",1,[68,73,74],{},"from unittest.mock import AsyncMock, MagicMock, call\n",[68,76,78],{"class":70,"line":77},2,[68,79,81],{"emptyLinePlaceholder":80},true,"\n",[68,83,85],{"class":70,"line":84},3,[68,86,81],{"emptyLinePlaceholder":80},[68,88,90],{"class":70,"line":89},4,[68,91,92],{},"async def test_order_is_persisted_before_the_event_is_published():\n",[68,94,96],{"class":70,"line":95},5,[68,97,98],{},"    parent = MagicMock()\n",[68,100,102],{"class":70,"line":101},6,[68,103,104],{},"    repo_save = AsyncMock()\n",[68,106,108],{"class":70,"line":107},7,[68,109,110],{},"    bus_publish = AsyncMock()\n",[68,112,114],{"class":70,"line":113},8,[68,115,116],{},"    # Children record into the parent's mock_calls in real time order.\n",[68,118,120],{"class":70,"line":119},9,[68,121,122],{},"    parent.attach_mock(repo_save, \"save\")\n",[68,124,126],{"class":70,"line":125},10,[68,127,128],{},"    parent.attach_mock(bus_publish, \"publish\")\n",[68,130,132],{"class":70,"line":131},11,[68,133,81],{"emptyLinePlaceholder":80},[68,135,137],{"class":70,"line":136},12,[68,138,139],{},"    service = OrderService(save=repo_save, publish=bus_publish)\n",[68,141,143],{"class":70,"line":142},13,[68,144,145],{},"    await service.place(an_order(id=\"ord_1\"))\n",[68,147,149],{"class":70,"line":148},14,[68,150,81],{"emptyLinePlaceholder":80},[68,152,154],{"class":70,"line":153},15,[68,155,156],{},"    # The sequence is the contract: write, then announce.\n",[68,158,160],{"class":70,"line":159},16,[68,161,162],{},"    assert [name for name, _args, _kwargs in parent.mock_calls] == [\"save\", \"publish\"]\n",[68,164,166],{"class":70,"line":165},17,[68,167,168],{},"    # And both coroutines were actually awaited, not just created.\n",[68,170,172],{"class":70,"line":171},18,[68,173,174],{},"    repo_save.assert_awaited_once()\n",[68,176,178],{"class":70,"line":177},19,[68,179,180],{},"    bus_publish.assert_awaited_once_with(\"order_created\", {\"order_id\": \"ord_1\"})\n",[182,183,186,308],"figure",{"className":184},[185],"diagram",[187,188,195,196,195,200,195,204,195,222,195,230,195,240,195,249,195,255,195,259,195,262,195,266,195,270,195,276,195,284,195,287,195,295,195,300,195,304],"svg",{"viewBox":189,"role":190,"ariaLabelledBy":191,"xmlns":194},"0 0 820 262","img",[192,193],"ord3-t","ord3-d","http:\u002F\u002Fwww.w3.org\u002F2000\u002Fsvg","\n  ",[197,198,199],"title",{"id":192},"Per-mock records versus a shared parent record",[201,202,203],"desc",{"id":193},"Two independent AsyncMocks each record their own awaits, so neither list reveals the relative order of save and publish. Attaching both to a shared parent produces one combined list in real time order, from which a single comparison asserts that save came before publish.",[205,206,207,208,195],"defs",{},"\n    ",[209,210,217],"marker",{"id":211,"viewBox":212,"refX":213,"refY":214,"markerWidth":215,"markerHeight":215,"orient":216},"ord3-a","0 0 10 10","9","5","7","auto-start-reverse",[218,219],"path",{"d":220,"fill":221},"M0 0 L10 5 L0 10 z","#81b29a",[223,224],"rect",{"x":225,"y":225,"width":226,"height":227,"rx":228,"fill":229},"0","820","262","14","#fffdf8",[231,232,239],"text",{"x":233,"y":234,"textAnchor":235,"fontSize":236,"fontWeight":237,"fill":238},"410","28","middle","16","700","#3d405b","Each mock knows its own history; the parent knows the order",[223,241],{"x":242,"y":243,"width":244,"height":245,"rx":246,"fill":247,"stroke":238,"strokeWidth":248},"26","56","220","70","11","#f4f1de","1.6",[231,250,254],{"x":251,"y":252,"textAnchor":235,"fontSize":253,"fontWeight":237,"fill":238},"136","84","12","save.await_args_list",[231,256,258],{"x":251,"y":257,"textAnchor":235,"fontSize":246,"fill":238},"106","[call(order)]",[223,260],{"x":242,"y":261,"width":244,"height":245,"rx":246,"fill":247,"stroke":238,"strokeWidth":248},"150",[231,263,265],{"x":251,"y":264,"textAnchor":235,"fontSize":253,"fontWeight":237,"fill":238},"178","publish.await_args_list",[231,267,269],{"x":251,"y":268,"textAnchor":235,"fontSize":246,"fill":238},"200","[call(\"order_created\", …)]",[231,271,275],{"x":272,"y":273,"textAnchor":235,"fontSize":246,"fill":274},"300","140","#8f3d22","which first?",[70,277],{"x1":278,"y1":279,"x2":280,"y2":281,"stroke":221,"strokeWidth":282,"markerEnd":283},"250","91","440","130","1.8","url(#ord3-a)",[70,285],{"x1":278,"y1":286,"x2":280,"y2":261,"stroke":221,"strokeWidth":282,"markerEnd":283},"185",[223,288],{"x":289,"y":290,"width":291,"height":292,"rx":253,"fill":293,"stroke":221,"strokeWidth":294},"446","96","348","90","#e6f0ea","2",[231,296,299],{"x":297,"y":298,"textAnchor":235,"fontSize":253,"fontWeight":237,"fill":238},"620","124","parent.mock_calls",[231,301,303],{"x":297,"y":302,"textAnchor":235,"fontSize":246,"fill":238},"148","[call.save(order),",[231,305,307],{"x":297,"y":306,"textAnchor":235,"fontSize":246,"fill":238},"168"," call.publish(\"order_created\", …)]",[309,310,311],"figcaption",{},"The per-mock lists answer \"how was this called\"; only the parent's list answers \"in what order did things happen\".",[26,313,315],{"id":314},"why-this-works","Why this works",[10,317,318,321,322,325],{},[14,319,320],{},"attach_mock"," makes a child mock report its calls to the parent as well as recording them itself. The parent's ",[14,323,324],{},"mock_calls"," is an ordered list, appended to at the moment each call is made, so it reflects the real interleaving of calls across every attached child. Because asyncio runs one coroutine at a time on a thread, the order in which calls are made is a faithful record of the order the code reached them.",[10,327,328,329,331,332,335],{},"The recorded entries are calls, not awaits. An ",[14,330,20],{}," child records the call when the coroutine is created, and the await separately in its own ",[14,333,334],{},"await_args_list",". Checking both — order via the parent, completion via each child — is what makes the assertion airtight.",[10,337,338,339,342],{},"The parent itself is an ordinary ",[14,340,341],{},"MagicMock"," and never needs to be passed to the code under test; it exists purely as a recorder. The code receives the children, exactly as it would receive real collaborators, and has no idea it is being observed collectively. That keeps the technique non-invasive: no production code changes, no test-only hooks, just a different way of constructing the doubles the test was going to create anyway.",[26,344,346],{"id":345},"edge-cases-and-failure-modes","Edge cases and failure modes",[31,348,349,356,374,393,402],{},[34,350,351,355],{},[352,353,354],"strong",{},"Ordering an implementation detail."," Two independent reads can happen in either order. Asserting one makes the test brittle. Assert order only where the domain requires it.",[34,357,358,361,362,365,366,368,369,373],{},[352,359,360],{},"Concurrent awaits."," Code that uses ",[14,363,364],{},"gather"," or a task group starts coroutines in one order and completes them in another. ",[14,367,324],{}," records the ",[370,371,372],"em",{},"start"," order. If completion order matters, give the doubles side effects that record completion.",[34,375,376,379,380,383,384,386,387,389,390,392],{},[352,377,378],{},"Children created by attribute access."," ",[14,381,382],{},"parent.save"," auto-created on a ",[14,385,341],{}," is a plain ",[14,388,341],{},", not an ",[14,391,20],{},". Create the async children explicitly and attach them.",[34,394,395,379,398,401],{},[352,396,397],{},"Autospecced children.",[14,399,400],{},"create_autospec"," mocks can be attached too, and keep their signature checks. Attach them after creation, not by building them from the parent.",[34,403,404,379,407,410],{},[352,405,406],{},"Resetting between phases.",[14,408,409],{},"parent.reset_mock()"," clears the combined record, which is useful when a test has a setup phase whose calls should not count.",[26,412,414],{"id":413},"start-order-versus-completion-order","Start order versus completion order",[10,416,417,418,420,421,424,425,428,429,431,432,434],{},"The parent's ",[14,419,324],{}," records when each coroutine was ",[370,422,423],{},"created",", which for sequential awaits is the same as when it ran. Concurrent code breaks that equivalence. Under ",[14,426,427],{},"asyncio.gather(save(), publish())"," both coroutines are created before either runs, so ",[14,430,324],{}," shows them in argument order regardless of which actually finished first — and asserting on it tests the order of the arguments to ",[14,433,364],{},", not the behaviour of the system.",[10,436,437,438,441,442,445],{},"When completion order matters under concurrency, the doubles themselves have to record it. A ",[14,439,440],{},"side_effect"," that appends to a shared list when the coroutine ",[370,443,444],{},"finishes"," gives the test the real sequence.",[59,447,449],{"className":61,"code":448,"language":63,"meta":64,"style":64},"import asyncio\nfrom unittest.mock import AsyncMock\n\n\nasync def test_both_writes_complete_before_ack():\n    finished: list[str] = []\n\n    def recording(name, delay):\n        async def run(*args, **kwargs):\n            await asyncio.sleep(delay)          # a real suspension point\n            finished.append(name)\n        return run\n\n    primary = AsyncMock(side_effect=recording(\"primary\", 0.02))\n    replica = AsyncMock(side_effect=recording(\"replica\", 0.01))\n    ack = AsyncMock(side_effect=recording(\"ack\", 0))\n\n    await replicated_write(primary, replica, ack, payload={\"id\": 1})\n\n    # Replica finishes first, but ack must come after BOTH, whatever their order.\n    assert finished[-1] == \"ack\"\n    assert set(finished[:2]) == {\"primary\", \"replica\"}\n",[14,450,451,456,461,465,469,474,479,483,488,493,498,503,508,512,517,522,527,531,536,540,546,552],{"__ignoreMap":64},[68,452,453],{"class":70,"line":71},[68,454,455],{},"import asyncio\n",[68,457,458],{"class":70,"line":77},[68,459,460],{},"from unittest.mock import AsyncMock\n",[68,462,463],{"class":70,"line":84},[68,464,81],{"emptyLinePlaceholder":80},[68,466,467],{"class":70,"line":89},[68,468,81],{"emptyLinePlaceholder":80},[68,470,471],{"class":70,"line":95},[68,472,473],{},"async def test_both_writes_complete_before_ack():\n",[68,475,476],{"class":70,"line":101},[68,477,478],{},"    finished: list[str] = []\n",[68,480,481],{"class":70,"line":107},[68,482,81],{"emptyLinePlaceholder":80},[68,484,485],{"class":70,"line":113},[68,486,487],{},"    def recording(name, delay):\n",[68,489,490],{"class":70,"line":119},[68,491,492],{},"        async def run(*args, **kwargs):\n",[68,494,495],{"class":70,"line":125},[68,496,497],{},"            await asyncio.sleep(delay)          # a real suspension point\n",[68,499,500],{"class":70,"line":131},[68,501,502],{},"            finished.append(name)\n",[68,504,505],{"class":70,"line":136},[68,506,507],{},"        return run\n",[68,509,510],{"class":70,"line":142},[68,511,81],{"emptyLinePlaceholder":80},[68,513,514],{"class":70,"line":148},[68,515,516],{},"    primary = AsyncMock(side_effect=recording(\"primary\", 0.02))\n",[68,518,519],{"class":70,"line":153},[68,520,521],{},"    replica = AsyncMock(side_effect=recording(\"replica\", 0.01))\n",[68,523,524],{"class":70,"line":159},[68,525,526],{},"    ack = AsyncMock(side_effect=recording(\"ack\", 0))\n",[68,528,529],{"class":70,"line":165},[68,530,81],{"emptyLinePlaceholder":80},[68,532,533],{"class":70,"line":171},[68,534,535],{},"    await replicated_write(primary, replica, ack, payload={\"id\": 1})\n",[68,537,538],{"class":70,"line":177},[68,539,81],{"emptyLinePlaceholder":80},[68,541,543],{"class":70,"line":542},20,[68,544,545],{},"    # Replica finishes first, but ack must come after BOTH, whatever their order.\n",[68,547,549],{"class":70,"line":548},21,[68,550,551],{},"    assert finished[-1] == \"ack\"\n",[68,553,555],{"class":70,"line":554},22,[68,556,557],{},"    assert set(finished[:2]) == {\"primary\", \"replica\"}\n",[10,559,560],{},"The assertion is deliberately partial. It pins down the one ordering the contract requires — acknowledge only after both writes — and leaves the order of the two concurrent writes free, because the system does not promise anything about it. Over-specifying concurrent order is the fastest way to write a test that fails intermittently for no reason anyone can fix. The discipline is to write down, before writing the assertion, exactly which pairs of operations the contract orders — and then to assert those pairs and nothing more. Everything else about the sequence is free to vary, and the test should let it. Tests written that way stay green through refactors and fail only when a real guarantee is broken.",[182,562,564,628],{"className":563},[185],[187,565,195,570,195,573,195,576,195,580,195,585,195,592,195,598,195,602,195,605,195,610,195,613,195,617,195,621,195,624],{"viewBox":566,"role":190,"ariaLabelledBy":567,"xmlns":194},"0 0 800 234",[568,569],"co-t","co-d",[197,571,572],{"id":568},"Creation order versus completion order under gather",[201,574,575],{"id":569},"Under gather, the primary and replica coroutines are created in argument order but complete in whichever order their I\u002FO finishes. mock_calls reflects creation order only. A side effect that records completion captures the real sequence, which lets the test assert that the acknowledgement follows both writes without constraining their relative order.",[223,577],{"x":225,"y":225,"width":578,"height":579,"rx":228,"fill":229},"800","234",[231,581,584],{"x":582,"y":234,"textAnchor":235,"fontSize":583,"fontWeight":237,"fill":238},"400","15.5","Record the order you actually mean",[223,586],{"x":242,"y":587,"width":588,"height":589,"rx":253,"fill":590,"stroke":591,"strokeWidth":294},"50","360","164","#f7f0da","#f2cc8f",[231,593,597],{"x":594,"y":595,"textAnchor":235,"fontSize":596,"fontWeight":237,"fill":238},"206","76","12.5","mock_calls (creation)",[231,599,601],{"x":600,"y":257,"fontSize":246,"fill":238},"44","primary, replica, ack",[231,603,604],{"x":600,"y":281,"fontSize":246,"fill":238},"always argument order",[231,606,609],{"x":600,"y":607,"fontSize":246,"fontWeight":237,"fill":608},"170","#8a5a00","says nothing about completion",[223,611],{"x":612,"y":587,"width":588,"height":589,"rx":253,"fill":293,"stroke":221,"strokeWidth":294},"414",[231,614,616],{"x":615,"y":595,"textAnchor":235,"fontSize":596,"fontWeight":237,"fill":238},"594","finished (completion)",[231,618,620],{"x":619,"y":257,"fontSize":246,"fill":238},"432","replica, primary, ack",[231,622,623],{"x":619,"y":281,"fontSize":246,"fill":238},"the real sequence",[231,625,627],{"x":619,"y":607,"fontSize":246,"fontWeight":237,"fill":626},"#2a5f49","assert ack is last, writes unordered",[309,629,630],{},"Sequential code can use either record. Concurrent code must use the completion record and assert only the orderings the contract promises.",[26,632,634],{"id":633},"when-order-is-the-contract","When order is the contract",[10,636,637],{},"The most common legitimate ordering requirement in async systems is the dual write: persist something, then tell the world about it. Publishing first means a consumer can receive an event for a record that does not exist yet — or never will, if the write then fails. Persisting first means that if publishing fails, the record exists without its announcement, which is recoverable with an outbox or a retry. The order is not a stylistic preference; it decides which failure mode the system has.",[10,639,640,641,644,645,648],{},"Tests of that requirement should assert order ",[370,642,643],{},"and"," test the failure between the steps. Make the save succeed and the publish raise, and assert the record exists and the error propagated; make the save raise, and assert that publish was never called at all. The second assertion — ",[14,646,647],{},"bus_publish.assert_not_awaited()"," — is often the more important one, because it is what proves no phantom event can escape when the write fails.",[10,650,651],{},"Other orderings worth pinning down in the same way include acquiring a lock before reading state it protects, checking authorisation before performing an action, and closing a stream before reporting completion. In each case the ordering encodes a safety property, and a test that asserts it is documenting that property as much as checking it.",[10,653,654,655,658,659,662],{},"A useful habit is to name such tests after the property rather than the mechanism — ",[14,656,657],{},"test_event_is_never_published_for_an_unsaved_order"," rather than ",[14,660,661],{},"test_save_called_before_publish",". The first survives a refactor that replaces the direct publish with an outbox, because the property still holds and only the assertion's mechanics need updating; the second invites someone to delete it as obsolete. Tests named for guarantees tend to be kept, and tests named for implementation tend to be discarded at exactly the moment the implementation changes and the guarantee most needs checking.",[182,664,666,723],{"className":665},[185],[187,667,195,672,195,675,195,678,195,681,195,684,195,689,195,692,195,695,195,698,195,702,195,706,195,708,195,711,195,714,195,717,195,720],{"viewBox":668,"role":190,"ariaLabelledBy":669,"xmlns":194},"0 0 800 244",[670,671],"dw-t","dw-d",[197,673,674],{"id":670},"Why persist-then-publish is the safe order",[201,676,677],{"id":671},"Two orders for a dual write. Publishing first risks a consumer receiving an event for a record that was never persisted if the write then fails, which is unrecoverable. Persisting first risks a record without its event if publishing fails, which an outbox or retry can recover. The tests assert the safe order and that no publish occurs when the save fails.",[223,679],{"x":225,"y":225,"width":578,"height":680,"rx":228,"fill":229},"244",[231,682,683],{"x":582,"y":234,"textAnchor":235,"fontSize":583,"fontWeight":237,"fill":238},"The order decides which failure you can recover from",[223,685],{"x":242,"y":587,"width":588,"height":686,"rx":253,"fill":687,"stroke":688,"strokeWidth":294},"172","#fbe9e3","#e07a5f",[231,690,691],{"x":594,"y":595,"textAnchor":235,"fontSize":596,"fontWeight":237,"fill":238},"publish, then save",[231,693,694],{"x":600,"y":257,"fontSize":246,"fill":238},"event sent · write fails",[231,696,697],{"x":600,"y":281,"fontSize":246,"fill":238},"consumers act on a record",[231,699,701],{"x":600,"y":700,"fontSize":246,"fill":238},"152","that does not exist",[231,703,705],{"x":600,"y":704,"fontSize":246,"fontWeight":237,"fill":274},"192","not recoverable",[223,707],{"x":612,"y":587,"width":588,"height":686,"rx":253,"fill":293,"stroke":221,"strokeWidth":294},[231,709,710],{"x":615,"y":595,"textAnchor":235,"fontSize":596,"fontWeight":237,"fill":238},"save, then publish",[231,712,713],{"x":619,"y":257,"fontSize":246,"fill":238},"write succeeds · publish fails",[231,715,716],{"x":619,"y":281,"fontSize":246,"fill":238},"record exists without event",[231,718,719],{"x":619,"y":700,"fontSize":246,"fill":238},"outbox or retry sends it later",[231,721,722],{"x":619,"y":704,"fontSize":246,"fontWeight":237,"fill":626},"recoverable",[309,724,725],{},"Asserting the order is how a test encodes this reasoning; asserting that a failed save produces no publish is how it proves it.",[10,727,728],{},"When ordering requirements multiply across a service, it is worth listing them in one place — a short comment block or a design note — so each has exactly one test and none is asserted incidentally elsewhere.",[26,730,732],{"id":731},"frequently-asked-questions","Frequently Asked Questions",[10,734,735,738,739,742,743,745],{},[352,736,737],{},"How do I check the order of awaits across two different mocks?","\nAttach both mocks to a common parent with ",[14,740,741],{},"parent.attach_mock(child, \"name\")",". The parent's ",[14,744,324],{}," then records every call to every child in the order they happened, so a single list comparison asserts the global sequence.",[10,747,748,751,752,754,755,757,758,761,762,765],{},[352,749,750],{},"Does mock_calls distinguish a call from an await?","\nNo. ",[14,753,324],{}," records the call that created the coroutine. For ",[14,756,20],{}," children, pair the ordering check with ",[14,759,760],{},"await_count"," or ",[14,763,764],{},"assert_awaited"," on each child to confirm the coroutines were actually awaited, not just created.",[10,767,768,771],{},[352,769,770],{},"When is asserting order a bad idea?","\nWhen the order is an implementation detail rather than a requirement. Two independent reads can happen in either order; asserting a specific one makes the test fail on a harmless refactor. Assert order only where the domain demands it — write before notify, lock before read, commit before publish.",[26,773,775],{"id":774},"related","Related",[31,777,778,784,791,798],{},[34,779,780,783],{},[50,781,782],{"href":52},"Patching Async Code & Coroutines"," — the rules for creating the async doubles.",[34,785,786,790],{},[50,787,789],{"href":788},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fdeep-dive-into-unittestmock\u002Fassert-called-with-vs-call-args-list\u002F","assert_called_with vs call_args_list"," — reading per-mock call histories.",[34,792,793,797],{},[50,794,796],{"href":795},"\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Fpatching-an-async-context-manager\u002F","Patching an Async Context Manager"," — ordering enter, body and exit.",[34,799,800,804],{},[50,801,803],{"href":802},"\u002Ftesting-async-and-concurrent-python\u002Ftimeouts-cancellation-and-deadlines\u002Ftesting-cancellation-and-cleanup-paths\u002F","Testing Cancellation and Cleanup Paths"," — ordering that must hold even when a task is cancelled.",[10,806,807,808],{},"← Back to ",[50,809,782],{"href":52},[811,812,813],"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":64,"searchDepth":77,"depth":77,"links":815},[816,817,818,819,820,821,822,823],{"id":28,"depth":77,"text":29},{"id":56,"depth":77,"text":57},{"id":314,"depth":77,"text":315},{"id":345,"depth":77,"text":346},{"id":413,"depth":77,"text":414},{"id":633,"depth":77,"text":634},{"id":731,"depth":77,"text":732},{"id":774,"depth":77,"text":775},"Verify the order of awaits across several AsyncMocks with a shared parent, await_args_list and mock_calls, and know when ordering is a real contract worth asserting.","md",{"slug":827,"type":828,"breadcrumb":829,"datePublished":830,"dateModified":830,"faq":831,"howto":838},"asserting-await-order-with-asyncmock","article","Await Order","2026-09-18",[832,834,836],{"q":737,"a":833},"Attach both mocks to a common parent with parent.attach_mock(child, 'name'). The parent's mock_calls then records every call to every child in the order they happened, so a single list comparison asserts the global sequence.",{"q":750,"a":835},"No. mock_calls records the call that created the coroutine. For AsyncMock children, pair the ordering check with await_count or assert_awaited on each child to confirm the coroutines were actually awaited, not just created.",{"q":770,"a":837},"When the order is an implementation detail rather than a requirement. Two independent reads can happen in either order; asserting a specific one makes the test fail on a harmless refactor. Assert order only where the domain demands it — write before notify, lock before read, commit before publish.",{"name":839,"description":840,"steps":841},"How to assert the order of awaits across collaborators","Attach the async doubles to one parent, run the code, and compare the parent's recorded calls to the required sequence while confirming each was awaited.",[842,845,848,851,854],{"name":843,"text":844},"Decide whether order is a requirement","Assert ordering only where the domain requires it, such as persisting before publishing.",{"name":846,"text":847},"Create the children as AsyncMocks","Build one AsyncMock per collaborator method whose ordering matters.",{"name":849,"text":850},"Attach them to a shared parent","Use parent.attach_mock so every child call is recorded in the parent's mock_calls.",{"name":852,"text":853},"Compare the recorded sequence","Assert on the names in parent.mock_calls against the required order.",{"name":855,"text":856},"Confirm each was awaited","Check await_count on each child so created-but-unawaited coroutines cannot pass.","\u002Fadvanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Fasserting-await-order-with-asyncmock",{"title":5,"description":824},"advanced-mocking-test-doubles-in-python\u002Fpatching-async-code-and-coroutines\u002Fasserting-await-order-with-asyncmock\u002Findex","t9iO_tPnA1Q-Oip6uY5qVUHACfJSMZjZGhZEDAJT9rs",1789718768950]