skip to content

Authoring and Fixtures

The structural pieces of a Go test file that outlive the assertion: t.Parallel ordering, fixtures under testdata/ and golden files a flag regenerates. This is where table tests break.

part ofGo (Golang)overview, primer and where to startread it →
on this pageshow

explore

questions

page 1 of 2

Go's testing package has no assertEqual - how do you compare a value against an expected one in a test?

level: juniorimportance: must knowfreq 70%

answer

  1. no assert helpers ship with Go
  2. the comparison is an ordinary if
  3. print both values, not a verdict
  4. got first, then want
  5. name the call and its inputs

basics

~20 s

You compare with ordinary Go code: == for comparable types, reflect.DeepEqual or slices.Equal for composites. On a mismatch call t.Errorf and print both values, conventionally the value you got first, then the value you wanted.

solid answer

~40 s

Go's `testing` package deliberately ships no assertion helpers, so a comparison is just an `if`. Pick the comparison that fits the type: `==` works for comparable types such as numbers, strings and structs whose fields are all comparable; slices, maps and functions are not comparable, so they need `reflect.DeepEqual` or, for slices of comparable elements, `slices.Equal`. When the comparison fails, call `t.Errorf` with a message that names the call, the inputs that identify the case, the value you got and the value you wanted - the house style is `Total(items) = 499, want 500`. Printing both sides is the whole point: a message that only says "mismatch" forces the next reader to reproduce the failure locally before they can start. Use `t.Fatalf` instead when carrying on would only produce noise.

code

go · 7 lines
go
func TestTotal(t *testing.T) {
	items := []Item{{Cents: 199}, {Cents: 301}}
	got := Total(items)
	if want := 500; got != want {
		t.Errorf("Total(%v) = %d, want %d", items, got, want)
	}
}

go deeper

for a junior

Be ready to write the assertion from memory: an if that compares, then t.Errorf printing the call, the value you got and the value you wanted. Interviewers are checking you know no assert helper ships with Go.

for a middle

Expect to explain which comparison fits which type - == for comparable values, reflect.DeepEqual or slices.Equal for slices and maps - and why == on a struct holding a slice does not compile at all.

for a senior

Show that you treat the failure message as the deliverable: someone reading a CI log on another team should identify the case and the difference without rerunning anything. Say what you put in and what you leave out.

for a principal

Own the consistency argument. A package whose tests all report mismatches the same way is greppable and diffable across years; be ready to say when a shared comparison helper is worth its maintenance and when it just hides the message.

## Go has no assertion vocabulary, on purpose Many test frameworks give you a sentence-shaped API - `assertEquals`, `expect(x).toBe(y)`. Go's standard `testing` package gives you none of that. A test is a plain function, a comparison is a plain `if`, and reporting a failure is a call to `t.Errorf` or `t.Fatalf`. Nothing is magic, which means nothing is hidden - and it also means the quality of a test's failure output is entirely on the person who wrote it. That splits the job into two decisions: **which comparison**, and **what the failure message says**. ## Choosing the comparison - **`==`** works for *comparable* types: numbers, strings, booleans, pointers, channels, interfaces, and structs or arrays whose fields are all themselves comparable. This is the default and you should reach for it first - it is exact, it is fast, and it is checked at compile time. - **A struct containing a slice, a map or a function is not comparable**, and `got == want` on one does not compile. That compile error is the usual reason a test author reaches for something else. - **`reflect.DeepEqual(got, want)`** walks two values recursively and reports whether they are deeply equal. It works on anything, which is exactly why it needs care: it compares every field, including unexported ones, it treats a nil slice and an empty non-nil slice as different, and it reports false for two `NaN` float values. - **`slices.Equal(got, want)`** compares two slices of comparable elements element by element. It is checked at compile time, it is cheaper than reflection, and it reports true for any two length-zero slices. `maps.Equal` is its counterpart for maps. - **Compare one field** when only one field is the point of the case. A narrow assertion produces a narrow failure message. ## Writing the failure message The convention throughout the standard library's own tests is to make the message read like the expression that was evaluated, followed by the expectation: ``` Total(items) = 499, want 500 Apply(order, "DE") = 119, want 120 ``` Three things earn their place in that line: 1. **The call and the inputs that identify the case.** Someone reading a CI log should know which case failed without opening the file. 2. **The value you got.** First, because the reader is looking for what the code did. 3. **The value you wanted.** Second, after `want`. The verb matters less than the consistency. A package where half the messages read `want 500, got 499` and half read the other way forces every reader to work out which number is which, and a reversed message sends people hunting for a bug on the wrong side. Pick the verb to match the value: - `%v` for most values. - `%q` for strings, so trailing whitespace and empty strings are visible. - `%+v` for structs, so field names appear. - `%#v` when the two sides print identically under `%v` - a nil slice and an empty slice both print as `[]`, and a message reading `got [], want []` helps nobody. ## Error or Fatal `t.Errorf` records the failure and lets the function continue, so a test that checks several independent things reports all of them in one run. `t.Fatalf` stops the test function immediately - the right choice when continuing would dereference something you just failed to obtain, because the follow-on failures would be noise. ## What this looks like in a library other teams import Suppose you maintain a small money and tax library and `Total` returns the order total in minor units: ```go func TestTotal(t *testing.T) { items := []Item{{Cents: 199}, {Cents: 301}} got := Total(items) if want := 500; got != want { t.Errorf("Total(%v) = %d, want %d", items, got, want) } } ``` The comparison is one line. The message tells a reader on a different team, reading a failed CI job on a dependency bump, exactly what was fed in and what came back. That is the entire craft here: Go gives you no assertion helper, so the assertion and its message are yours to write well. ## Anti-patterns worth naming - `t.Error("mismatch")` - true and useless. - Comparing formatted strings (`fmt.Sprint(got) == fmt.Sprint(want)`) instead of values, which silently equates values of different types. - Panicking instead of reporting, which loses the test framework's file and line attribution. - Reversing got and want in some tests but not others.

  • Why does the convention put the value you got before the value you wanted?
    It makes the message read like the code that was executed: the call, then what it produced, then the expectation. Consistency matters more than the choice itself. A package that mixes both orders forces every reader to work out which number is which, and one reversed message sends people looking for a bug on the wrong side of the comparison.
  • What belongs in the message besides the two values?
    The call and the inputs that identify the case, so `Apply(order, "DE") = 119, want 120` tells a reader in a CI log which case failed without opening the file. Keep the values raw rather than describing them in prose - "the total was wrong" discards the one thing the reader needs. If the test is a named subtest, the case name already carries some of that context.
  • Would you compare a whole struct or field by field?
    Compare the whole value when every field is part of the contract you mean to pin: one comparison, one message. Compare field by field when only some fields matter, when others hold timestamps or generated ids, or when a whole-struct message would be unreadable. A middle path is to build a reduced value holding only the fields you are asserting, then compare that.

A failure line is a bug report written in advance: it should say what was run and what came back, not merely that something went wrong.

saying these in an interview costs you the question

  • Claims Go tests cannot compare values without an external assertion library
  • Reports only that the test failed, printing neither value
  • Prints want before got so every message in the package reads backwards
  • Uses == on a struct containing a slice and is surprised it does not compile
  • Compares fmt.Sprint output instead of the values themselves
open as a page

What does declaring `package foo_test` in a _test.go file change versus declaring `package foo`?

level: juniorimportance: must knowfreq 58%

basics

~20 s

A _test.go file declaring package foo compiles into the package itself and can use its unexported identifiers. One declaring package foo_test compiles as a separate package that must import foo and sees only its exported API.

open as a page

Why do Go tests keep fixture files in a directory named testdata, and how does the go command treat it?

level: juniorimportance: must knowfreq 50%

basics

~20 s

The go command ignores directories named testdata when matching package patterns, so nothing inside is compiled or vetted. A test binary runs with its own package directory as the working directory, so a relative path like testdata/user.go.golden always resolves.

open as a page

In a Go test, what does t.TempDir() return and who deletes that directory?

level: juniorimportance: must knowfreq 58%

basics

~20 s

t.TempDir() returns the path of a fresh, empty directory created for that test. The testing package removes it automatically once the test and all of its subtests have completed, so the test never writes its own removal code.

open as a page

What does calling t.Parallel() in a Go test do, and when does that test's body actually run?

level: juniorimportance: must knowfreq 60%

basics

~20 s

t.Parallel() marks a test as safe to run alongside other tests that also call it. The call pauses the test immediately; it resumes only after its parent test function has returned, and then runs concurrently with its parallel siblings.

open as a page

In Go, what is a table-driven test, and what does running each case through t.Run give you?

level: juniorimportance: must knowfreq 78%

basics

~20 s

A table-driven test declares a slice of case structs (name, input, want) and loops over them, calling t.Run(tc.name, ...) so every row becomes a named subtest that fails and can be re-run on its own.

open as a page

In a Go test, what is the difference between t.Error and t.Fatal?

level: juniorimportance: must knowfreq 82%

basics

~10 s

Both mark the test as failed. t.Error logs the message and lets the test function keep running, so one run can report several problems. t.Fatal logs and stops that test function immediately.

open as a page

A Go test asserting reflect.DeepEqual(got, []string{}) fails because got is a nil slice - how do you fix it?

level: middleimportance: must knowfreq 60%

basics

~20 s

reflect.DeepEqual reports false for a nil slice against an empty non-nil one. Decide what the function promises, then either write want to match it, compare with slices.Equal, which treats both as length zero, or assert len(got) == 0.

open as a page

In Go, what makes go test execute a func Example() and check its output?

level: juniorimportance: should knowfreq 50%

basics

~20 s

A trailing // Output: comment. go test compiles every Example function in a _test.go file but only runs one that ends with an // Output: comment, comparing what the example printed to stdout against that text after trimming surrounding whitespace.

open as a page

In a Go test, how do you assert that a call returned a particular error?

level: middleimportance: should knowfreq 55%

basics

~20 s

Use errors.Is against a sentinel error, or errors.As with a typed error when the test needs a field off it. Both see through wrapping. Comparing err.Error() text is brittle, and checking only that err is non-nil pins nothing.

open as a page

Why should a Go test compare two time.Time values with their Equal method rather than ==?

level: middleimportance: should knowfreq 45%

basics

~20 s

== compares every field of the time.Time struct, including the monotonic clock reading and the location pointer, so two values for the same instant can compare unequal. The Equal method compares the instant they represent.

open as a page

How do Go Example function names bind an example to a specific type or method?

level: middleimportance: should knowfreq 38%

basics

~20 s

By the name alone. Example documents the package, ExampleF a function or type F, and ExampleT_M the method M on type T. Any of those may carry a trailing lower-case suffix, such as ExampleParse_relativePath, to give one symbol several examples.

open as a page

How do you add an -update flag to a Go test so it rewrites its golden file in testdata?

level: middleimportance: should knowfreq 42%

basics

~20 s

Declare a package-level var in the test file with flag.Bool("update", false, ...). The testing package parses flags before tests run, so when it is set the test writes the fresh output with os.WriteFile, then reads it back and compares.

open as a page

Why does a deferred os.RemoveAll in a Go test delete the directory before its t.Parallel subtests run?

level: middleimportance: should knowfreq 50%

basics

~20 s

A subtest that calls t.Parallel() is paused until the parent test function returns, and the parent's deferred calls fire at exactly that return — before the subtests execute. Register the removal with t.Cleanup, which waits for subtests.

open as a page

Why does a Go test that calls t.Setenv panic if it also calls t.Parallel?

level: middleimportance: should knowfreq 42%

basics

~20 s

Environment variables belong to the whole process, so one test's t.Setenv value would leak into every test running at the same time. The testing package therefore panics when a test combines t.Setenv with t.Parallel, in either order.

open as a page

Why did Go table-driven tests before 1.22 start each loop iteration with tc := tc?

level: middleimportance: should knowfreq 56%

basics

~20 s

Before Go 1.22 a range loop reused one tc variable across iterations, so a closure outliving its iteration read the last row's values. The tc := tc line made a per-iteration copy; Go 1.22 made it unnecessary.

open as a page

How does go test -run select a single t.Run subtest, and how are subtest names formed?

level: middleimportance: should knowfreq 52%

basics

~20 s

A subtest's full name is the parent name, a slash, then the case name with spaces turned into underscores. go test -run splits its pattern on slashes and matches each part as an unanchored regexp.

open as a page

Why does a Go test report the failure line inside a helper function, and what does t.Helper fix?

level: middleimportance: should knowfreq 52%

basics

~20 s

t.Error and t.Fatal report the file and line where they were called, which is inside the helper. Calling t.Helper() at the top of the helper makes the testing package skip that frame and report the caller's line in the test instead.

open as a page

In a Go package, an Example function prints the wrong text yet go test passes. Why?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Almost certainly the example is never executed. An example runs only if the last comment in its body starts with // Output:, and only if the character after Example is upper-case; otherwise it is compiled, skipped and silently passes.

open as a page

Your package's tests all live in `package foo` and break on every internal refactor. What do you gain and lose by moving them to `package foo_test`?

level: seniorimportance: should knowfreq 42%

basics

~20 s

You gain tests bound to the exported contract, so behaviour-preserving refactors stop breaking them, plus honest API feedback and no test-only import cycles. You lose direct access to internal state, which returns only through a narrow seam.

open as a page

Why does a _test.go file in `package geom` that imports a helper importing geom fail to compile, and how does `package geom_test` fix it?

level: seniorimportance: should knowfreq 36%

basics

~20 s

Imports in a package foo test file count as imports of that package, so a helper importing foo closes a cycle the go command rejects. Package foo_test is downstream of both and nothing imports it, so no cycle exists.

open as a page

A code generator's golden test flakes, and each go test -update run rewrites the golden differently. What is the likely cause?

level: seniorimportance: should knowfreq 33%

basics

~20 s

The generator is almost certainly emitting in the order of a range over a Go map, and Go randomises that order deliberately. Sort the keys and emit in that fixed order, then assert determinism by generating twice and comparing.

open as a page

A Go test fails at teardown with 'TempDir RemoveAll cleanup: access is denied' on Windows — what causes it?

level: seniorimportance: should knowfreq 34%

basics

~20 s

Something still holds an open file inside the temp directory. Windows refuses to delete a file with a live handle, so the automatic removal registered by t.TempDir fails and reports an error, turning an otherwise passing test red.

open as a page

Several Go subtests calling t.Parallel mutate one map the parent test created. How do you diagnose and fix it?

level: seniorimportance: should knowfreq 50%

basics

~20 s

Subtests calling t.Parallel run concurrently after the parent returns, so a map their closures captured is shared, not copied. Confirm with go test -race, then give each subtest its own fixture or guard the shared one with a sync.Mutex.

open as a page

A Go test calls t.Fatal from a goroutine it started. Why is that wrong, and what should it do instead?

level: seniorimportance: should knowfreq 45%

basics

~20 s

t.Fatal stops only the goroutine that calls it, because FailNow uses runtime.Goexit. The test function itself keeps running, or blocks forever waiting for a value that goroutine will now never send. Hand the failure back to the test goroutine instead.

open as a page

When should a Go Example use // Unordered output: instead of // Output:?

level: middleimportance: nice to knowfreq 24%

basics

~20 s

When the example prints one line per item and the line order is not guaranteed, such as ranging over a map. With // Unordered output: the runner sorts the printed lines and the expected lines before comparing, so order stops mattering but content and count still do.

open as a page

What is an export_test.go file, and how does it let a `package foo_test` test reach unexported code?

level: middleimportance: nice to knowfreq 30%

basics

~20 s

A file declaring package foo whose name ends in _test.go, so go test compiles it but a normal build never does. It assigns unexported identifiers to exported names, giving external tests a seam absent from the shipped API.

open as a page

In a Go test, when is the context from t.Context() cancelled relative to t.Cleanup functions?

level: middleimportance: nice to knowfreq 24%

basics

~10 s

It is cancelled just before the test's registered cleanup functions are called. Every cleanup therefore sees an already-cancelled context, so teardown work that needs a live context must build its own from context.Background().

open as a page

In go test, how many tests run at once when they call t.Parallel, and does that span packages?

level: middleimportance: nice to knowfreq 28%

basics

~20 s

Inside one package's test binary, the number of tests running together is capped by go test's -parallel flag, which defaults to GOMAXPROCS. Separately, go test over several packages runs their binaries at the same time, capped by -p.

open as a page

How does t.Skip in a Go test differ from just returning early from the test function?

level: middleimportance: nice to knowfreq 40%

basics

~20 s

Returning early leaves the test reported as a normal pass. t.Skip logs a reason, marks the test as skipped, and stops it immediately, so the run records visibly that the test did not actually check anything.

open as a page

showing 1–30 of 33