Cleanup & defer destroy
Never leak real resources.
The single most important habit in Terratest is reliable cleanup, and the idiom is defer terraform.Destroy placed immediately after Options, before Apply. Go runs deferred calls when the function returns — including when an assertion fails or the test panics — so putting Destroy in a defer guarantees teardown runs no matter how the test ends. Skip it, or place it after the assertions, and a failing test leaves real resources (and a real bill) behind.
func TestThing(t *testing.T) {opts := &terraform.Options{ TerraformDir: "../examples/thing" }defer terraform.Destroy(t, opts) // FIRST — runs even if apply/asserts failterraform.InitAndApply(t, opts)// ... assertions that might fail ...}
When cleanup itself fails
Destroy can fail too — a dependency still in use, a resource stuck deleting, an eventual-consistency race. Guard against leaks in depth: use unique names/tags per test run so orphans are identifiable, run tests in a dedicated sandbox account you can periodically sweep, and add a scheduled cleanup job (e.g. cloud-nuke) that removes anything older than a few hours matching the test tag. Belt and suspenders, because a leaked NAT gateway or RDS instance quietly costs money for weeks.
import "github.com/gruntwork-io/terratest/modules/random"name := "test-" + strings.ToLower(random.UniqueId()) // unique per runopts := &terraform.Options{TerraformDir: "../examples/thing",Vars: map[string]interface{}{ "name": name, "tags": map[string]string{"terratest":"true"} },}// a scheduled cloud-nuke sweeps anything tagged terratest older than N hours