Go's testing package has no assertEqual - how do you compare a value against an expected one in a test?
answer
- no assert helpers ship with Go
- the comparison is an ordinary if
- print both values, not a verdict
- got first, then want
- name the call and its inputs
basics
~20 sYou 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 sGo'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 linesfunc 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
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.
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.
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.
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