What is useful for t.Cleanup?

Cleanup functions are also called if your test panics, so in your case both would work.

The advantage of using T.Cleanup() becomes clear if your test calls other functions, passing testing.T along. Obviously using defer in those functions would be executed before those functions return, but if you register cleanup functions using T.Cleanup(), then they will be called only at the end of your test.

Think of T.Cleanup() as an "improved" and extended version of defer. It also documents that the passed functions are for cleanup purposes.


t.Cleanup is useful for cleaning up resources allocated by a helper function when the test does not care about the resource itself.

Example

Consider testing a service layer. The service uses a *sql.DB but does not create it itself.

package testutils

import (
  "testing"

  "my/db"
  "my/domain"
)

func NewTestSubject(t *testing.T) *domain.Service {
  t.Helper()  
  sqldb := newDatabase(t)
  s, _ := domain.NewService(sqldb)
  return s
}

func newDatabase(t *testing.T) *sql.DB {
  t.Helper()
  d, _ := db.Create()
  t.Cleanup(func() {
    d.Close()
  })
}

Without t.Cleanup newTestSubject would have to return (*domain.Service, *sql.DB), leaking details about domain.Service's construction.


Besides what others have pointed out, t.Cleanup() is also useful when dealing with parallel subtests, where the cleanup should only run after all subtests have completed. Consider

func TestSomething(t *testing.T){
   setup()
   defer cleanup()
   t.Run("parallel subtest 1", func(t *testing.T){
      t.Parallel()
      (...)
   })
   t.Run("parallel subtest 2", func(t *testing.T){
      t.Parallel()
      (...)
   })
}

which doesn't work because the test function will return while the the subtests are still running, causing the resources required by the subtests to get wiped out by defer cleanup().

Before t.Cleanup() a way to solve this was to wrap the subtests on another test

func TestSomething(t *testing.T){
   setup()
   defer cleanup()
   t.Run("parallel tests", func(t *testing.T){
      t.Run("subtest 1", func(t *testing.T){
         t.Parallel()
         (...)
      })
      t.Run("subtest 2", func(t *testing.T){
         t.Parallel()
         (...)
      })
   })
}

which looks ok, but with t.Cleanup() it gets way better

func TestSomething(t *testing.T){
   setup()
   t.Cleanup(cleanup)
   t.Run("parallel subtest 1", func(t *testing.T){
      t.Parallel()
      (...)
   })
   t.Run("parallel subtest 2", func(t *testing.T){
      t.Parallel()
      (...)
   })
}