When several tests call the same function and repeat nearly identical assertions, adding the next case becomes tedious. A table-driven test puts the inputs and expected results together as data, then runs one shared check for each case. In Go, this keeps the assertion small and makes new scenarios easier to add. section 3.3
Listing 3.7 translator_test.go
package translation_test
import (
"testing"
"github.com/holmes89/hello-api/translation"
)
func TestTranslate(t *testing.T) {
// Arrange
tt := []struct { #1
Word string
Language string
Translation string
}{
{ #2
Word: "hello",
Language: "english",
Translation: "hello",
},
{
Word: "hello",
Language: "german",
Translation: "hallo",
},
{
Word: "hello",
Language: "finnish",
Translation: "hei",
},
{
Word: "hello",
Language: "dutch",
Translation: "",
},
}
for _, test := range tt { #3
// Act
res := translation.Translate(test.Word, test.Language) #4
// Assert
if res != test.Translation { #5
t.Errorf(
`expected "%s" to be "%s" from "%s" but received "%s"`,
test.Word, test.Language, test.Translation, res)
}
}
}
The translation test gathers words, languages, and expected translations in a slice of structs before checking the cases with shared test code. section 3.3
Start with the behavior already covered, run the tests after refactoring, and then add cases that challenge your assumptions. For a translator intended to handle only “hello,” an unfamiliar word is worth testing; casing and extra spaces are further input cases. Let failures guide the next small implementation change rather than designing far ahead. section 3.3
Listing 3.16 translator_test.go
func TestTranslateAPI(t *testing.T) {
tt := []struct { #1
Endpoint string
StatusCode int
ExpectedLanguage string
ExpectedTranslation string
}{
{
Endpoint: "/hello",
StatusCode: http.StatusOK,
ExpectedLanguage: "english",
ExpectedTranslation: "hello",
},
{
Endpoint: "/hello?language=german",
StatusCode: http.StatusOK,
ExpectedLanguage: "german",
ExpectedTranslation: "hallo",
},
}
handler := http.HandlerFunc(rest.TranslateHandler) #2
for _, test := range tt { #3
rr := httptest.NewRecorder()
req, _ := http.NewRequest("GET", test.Endpoint, nil)
handler.ServeHTTP(rr, req)
if rr.Code != test.StatusCode {
t.Errorf(`expected status %d but received %d`,
test.StatusCode, rr.Code)
}
var resp rest.Resp
json.Unmarshal(rr.Body.Bytes(), &resp)
if resp.Language != test.ExpectedLanguage {
t.Errorf(`expected language "%s" but received %s`,
test.ExpectedLanguage, resp.Language)
}
if resp.Translation != test.ExpectedTranslation {
t.Errorf(`expected Translation "%s" but received %s`,
test.ExpectedTranslation, resp.Translation)
}
}
}
The same pattern extends to an HTTP handler: each row supplies an endpoint and expected status, language, and translation, while the test loops through the rows. section 3.5
Add a missing-translation case
After testing successful requests for “/hello” and “/hello?language=german,” add “/hello?language=dutch” with an expected 404 status and empty response values. Run the test, observe the failure, and update the handler to return Not Found when no translation exists. section 3.5





