Functions That Return Errors
Many Go functions return two values: the result and an error. On success the error is nil; on failure the result is a zero value and the error explains what went wrong.
A test for such a function must check both paths: the good input returns the right value and a nil error, and the bad input actually returns an error — a function that silently accepts bad input is a bug too.
The Good Path
For a valid input, check the error first: if it is not nil, something is deeply wrong and checking the value makes no sense — that is a job for t.Fatalf. Then compare the value.
Valid input, nil error
SafeDivide(10, 2) must return 5 and no error. The error is checked first with t.Fatalf; only then is the value compared.
Output: Go test file and captured go test output
package calc
import (
"errors"
"testing"
)
func SafeDivide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
func TestSafeDivideGood(t *testing.T) {
got, err := SafeDivide(10, 2)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != 5 {
t.Errorf("SafeDivide(10, 2) = %v; want 5", got)
}
}
The Bad Path
The other half of the contract: bad input must produce an error. The test calls the function with the bad input and fails if the error is nil.
The result value is ignored with _ — when an error comes back, the value carries no meaning.
Bad input must return an error
Dividing by zero has to fail. If err comes back nil, the test fails with t.Fatal — the function accepted input it should have rejected.
Output: Go test file and captured go test output
package calc
import (
"errors"
"testing"
)
func SafeDivide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
func TestSafeDivideByZero(t *testing.T) {
_, err := SafeDivide(10, 0)
if err == nil {
t.Fatal("expected an error for division by zero, got nil")
}
}
A Table with wantErr
Both paths fit into one table-driven test: give each case a wantErr field saying whether an error is expected.
The loop checks the error side first — got an error when none was expected, or no error when one was expected, is fatal for that subtest. For the good cases it then compares the value as usual.
Good and bad cases in one table
Four cases: three normal divisions and one division by zero with wantErr: true. One loop covers the whole contract of the function.
Output: Go test file and captured go test output
package calc
import (
"errors"
"testing"
)
func SafeDivide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("cannot divide by zero")
}
return a / b, nil
}
func TestSafeDivide(t *testing.T) {
cases := []struct {
name string
a, b float64
want float64
wantErr bool
}{
{"simple division", 10, 2, 5, false},
{"negative result", -9, 3, -3, false},
{"divide zero", 0, 5, 0, false},
{"divide by zero", 10, 0, 0, true},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got, err := SafeDivide(tc.a, tc.b)
if (err != nil) != tc.wantErr {
t.Fatalf("SafeDivide(%v, %v) error = %v; wantErr %v", tc.a, tc.b, err, tc.wantErr)
}
if !tc.wantErr && got != tc.want {
t.Errorf("SafeDivide(%v, %v) = %v; want %v", tc.a, tc.b, got, tc.want)
}
})
}
}