{
  "$type": "site.standard.document",
  "canonicalUrl": "https://rednafi.com/go/deferred-teardown-closure/",
  "description": "Return teardown closures from test helpers to manage cleanup elegantly. Learn patterns for temp files, mock servers, and t.Cleanup() usage.",
  "path": "/go/deferred-teardown-closure/",
  "publishedAt": "2025-03-28T00:00:00.000Z",
  "site": "at://did:plc:fgtm2c26vfcj74rfmeggbyqj/site.standard.publication/3mnl6f7ob462z",
  "tags": [
    "Go",
    "Testing",
    "API"
  ],
  "textContent": "While watching [Mitchell Hashimoto's Advanced Testing with Go talk], I came across this neat\ntechnique for deferring teardown to the caller. Let's say you have a helper function in a\ntest that needs to perform some cleanup afterward.\n\nYou can't run the teardown inside the helper itself because the test still needs the setup.\nFor example, in the following case, the helper runs its teardown immediately:\n\nWhen helper is called, it defers its teardown - which executes at the end of the helper\nfunction, not the test. But the test logic still depends on whatever the helper set up. So\nthis approach doesn't work.\n\nThe next working option is to move the teardown logic into the test itself:\n\nThis works fine if you have only one helper. But with multiple helpers, it quickly becomes\nmessy - you now have to manage multiple teardown calls manually, like this:\n\nYou also need to be careful with the order: defer statements are executed in LIFO\n(last-in, first-out) order. So if teardown order matters, this can be a problem. Ideally,\nyour tests shouldn't depend on teardown order - but sometimes they do.\n\nSo rather than manually handling cleanup inside the test, have helpers return a teardown\nfunction that the test can defer itself. Here's how:\n\nEach helper is self-contained: it sets something up and returns a function to clean up\nwhatever resource it has spun up. The test controls when teardown happens by calling the\ncleanup function at the appropriate time. Another benefit is that the returned teardown\nclosure has access to the local variables of the helper. So func() can access the helper's\ntesting.T without us having to pass it explicitly as a parameter.\n\nHere's how I've been using this pattern.\n\nCreating a temporary file to test file I/O\n\nThe setupTempFile helper creates a temporary file, writes some content to it, and returns\nthe file name along with a teardown function that removes the file.\n\nIn the main test:\n\nRunning the test displays:\n\nStarting and stopping a mock HTTP server\n\nSometimes you want to test code that makes HTTP calls. Here's a helper that starts an\nin-memory mock server and returns its URL and a cleanup function that shuts it down:\n\nAnd in the test:\n\nRunning the test prints:\n\nSetting up and tearing down a database table\n\nIn tests that hit a real (or test) database, you often need to create and drop tables.\nHere's a helper that sets up a test table and returns a teardown function to drop it:\n\nAnd the test:\n\nThe t.Cleanup() method\n\n_P.S. I learned about this after the blog went live._\n\nGo 1.14 added the t.Cleanup() method, which lets you avoid returning the teardown closures\nfrom helper functions altogether. It also runs the cleanup logic in the correct order\n(LIFO). So, you could rewrite the first example in this post as follows:\n\nNow the testing package will handle calling the cleanup logic in the correct order. You\ncan add multiple teardown functions like this:\n\nThe functions will run in LIFO order. Similarly, the database setup example can be rewritten\nlike this:\n\nThen the helper function is used like this:\n\nFin!\n\n\n\n\n[mitchell hashimoto's advanced testing with go talk]:\n    https://www.youtube.com/watch?v=8hQG7QlcLBk",
  "title": "Deferred teardown closure in Go testing"
}