Algumas semanas atrás implementamos uma API utilizando gorilla/mux (link do post). No entanto, deixamos de lado algo que é muito importante, os famosos testes. Por isso, nesse post vamos corrigir essa falha e adicionar alguns testes ao nosso projeto.
Para começar, vamos criar um arquivo com o nome de hello_test.go dentro da pasta handlers. Nesse arquivo vamos adicionar um teste para a função HandleHello
.
func TestHandleHello(t *testing.T) { }
Com a função de testes iniciada, a primeira coisa que vamos fazer é criar uma struct anônima para nos auxiliar com uma massa de dados para teste. Dessa forma conseguiremos testar o mesmo endpoint com vários inputs diferentes.
Essa struct anônima terá apenas dois atributos, rVar (request variable) e expected.
func TestHandleHello(t *testing.T) { tt := []struct { rVar string expected string }{ {rVar: "Tiago", expected: "Hello Tiago"}, {rVar: "Lucas", expected: "Hello Lucas"}, {rVar: "Maria", expected: "Hello Maria"}, {rVar: "Dani", expected: "Hello Dani"}, } }
Próximo passo é iterar esse array de dados e criar uma nova request para o endpoint que queremos testar. Como no nosso caso o endpoint recebe um parâmetro, vamos usar a função fmt.Sprintf
para gerar o endpoint de forma dinâmica.
func TestHandleHello(t *testing.T) { tt := []struct { rVar string expected string }{ {rVar: "Tiago", expected: "Hello Tiago"}, {rVar: "Lucas", expected: "Hello Lucas"}, {rVar: "Maria", expected: "Hello Maria"}, {rVar: "Dani", expected: "Hello Dani"}, } for _, tc := range tt { path := fmt.Sprintf("/%s", tc.rVar) req, err := http.NewRequest("GET", path, nil) if err != nil { t.Fatal(err) } ... }
Feito isso, vamos inicializar uma struct do package net/http/httptest chamada ResponseRecorder
. Essa struct é uma implementação da http.ResponseWriter
que grava as mudanças, criando assim a possibilidade para inspecionar a response do endpoint.
func TestHandleHello(t *testing.T) { tt := []struct { rVar string expected string }{ {rVar: "Tiago", expected: "Hello Tiago"}, {rVar: "Lucas", expected: "Hello Lucas"}, {rVar: "Maria", expected: "Hello Maria"}, {rVar: "Dani", expected: "Hello Dani"}, } for _, tc := range tt { path := fmt.Sprintf("/%s", tc.rVar) req, err := http.NewRequest("GET", path, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() ... }
Agora que já conseguimos gravar a response, vamos criar um gorilla/mux router e configurar o endpoint assim como fizemos na nossa função main.
func TestHandleHello(t *testing.T) { tt := []struct { rVar string expected string }{ {rVar: "Tiago", expected: "Hello Tiago"}, {rVar: "Lucas", expected: "Hello Lucas"}, {rVar: "Maria", expected: "Hello Maria"}, {rVar: "Dani", expected: "Hello Dani"}, } for _, tc := range tt { path := fmt.Sprintf("/%s", tc.rVar) req, err := http.NewRequest("GET", path, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() router := mux.NewRouter() router.HandleFunc("/{name}", HandleHello).Methods("GET") ... }
Feito isso, vamos chamar um método do gorilla/mux router chamado ServeHTTP
. É nesse método que mora o pulo do gato, já que ele aceita dois parâmetros (httptest.ResponseRecorder
e *http.Request
).
func TestHandleHello(t *testing.T) { tt := []struct { rVar string expected string }{ {rVar: "Tiago", expected: "Hello Tiago"}, {rVar: "Lucas", expected: "Hello Lucas"}, {rVar: "Maria", expected: "Hello Maria"}, {rVar: "Dani", expected: "Hello Dani"}, } for _, tc := range tt { path := fmt.Sprintf("/%s", tc.rVar) req, err := http.NewRequest("GET", path, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() router := mux.NewRouter() router.HandleFunc("/{name}", HandleHello).Methods("GET") router.ServeHTTP(rr, req) ... }
Para finalizar, vamos fazer um decode da response e validar se o valor retornado é o que estava sendo esperado pelo teste.
import ( "encoding/json" "fmt" "net/http" "net/http/httptest" "testing" "github.com/gorilla/mux" ) func TestHandleHello(t *testing.T) { tt := []struct { rVar string expected string }{ {rVar: "Tiago", expected: "Hello Tiago"}, {rVar: "Lucas", expected: "Hello Lucas"}, {rVar: "Maria", expected: "Hello Maria"}, {rVar: "Dani", expected: "Hello Dani"}, } for _, tc := range tt { path := fmt.Sprintf("/%s", tc.rVar) req, err := http.NewRequest("GET", path, nil) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() router := mux.NewRouter() router.HandleFunc("/{name}", HandleHello).Methods("GET") router.ServeHTTP(rr, req) var response map[string]string json.Unmarshal(rr.Body.Bytes(), &response) if response["message"] != tc.expected { t.Errorf("wrong response body for param %s: got %v want %v", tc.rVar, rr.Body.String(), tc.expected) } } }
Estando na raiz do nosso projeto, executamos o comando go test ./handlers -v
. A resposta deve ser algo similar a isso:
=== RUN TestHandleHello
--- PASS: TestHandleHello (0.00s)
PASS
ok github.com/aprendagolang/gorilla/handlers
Para testar nosso outro endpoint, vamos criar um novo arquivo e chamá-lo de person_test.go. Nesse arquivo vamos criar a função TestHandlePerson
.
func TestHandlePerson(t *testing.T) { }
Seguindo a mesma lógica da outra função, vamos criar uma struct anônima com dois atributos. Como esse endpoint já tem uma struct para fazer o decode da request, vamos utiliza-la como o tipo do primeiro atributo. Já o segundo será uma string.
func TestHandlePerson(t *testing.T) { tt := []struct { rVar Person expected string }{ {rVar: Person{Name: "Tiago", Age: 31}, expected: "Nome: Tiago - Idade: 31\n"}, {rVar: Person{Name: "Lucas", Age: 41}, expected: "Nome: Lucas - Idade: 41\n"}, {rVar: Person{Name: "Maria", Age: 21}, expected: "Nome: Maria - Idade: 21\n"}, {rVar: Person{Name: "Dani", Age: 36}, expected: "Nome: Dani - Idade: 36\n"}, } }
Dentro do nosso looping, antes de criar a request, vamos fazer o Encode da struct Person
para que ela possa ser usada no payload.
func TestHandlePerson(t *testing.T) { tt := []struct { rVar Person expected string }{ {rVar: Person{Name: "Tiago", Age: 31}, expected: "Nome: Tiago - Idade: 31\n"}, {rVar: Person{Name: "Lucas", Age: 41}, expected: "Nome: Lucas - Idade: 41\n"}, {rVar: Person{Name: "Maria", Age: 21}, expected: "Nome: Maria - Idade: 21\n"}, {rVar: Person{Name: "Dani", Age: 36}, expected: "Nome: Dani - Idade: 36\n"}, } for _, tc := range tt { data, err := json.Marshal(tc.rVar) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/person", bytes.NewBuffer(data)) if err != nil { t.Fatal(err) } ... }
Para finalizar nossa função vamos:
- Criar um
ResponseRecorder
- Definir a rota no gorilla/mux
- Passar o
ResponseRecorder
e aRequest
para a funçãoServeHTTP
- Validar a response
func TestHandlePerson(t *testing.T) { tt := []struct { rVar Person expected string }{ {rVar: Person{Name: "Tiago", Age: 31}, expected: "Nome: Tiago - Idade: 31\n"}, {rVar: Person{Name: "Lucas", Age: 41}, expected: "Nome: Lucas - Idade: 41\n"}, {rVar: Person{Name: "Maria", Age: 21}, expected: "Nome: Maria - Idade: 21\n"}, {rVar: Person{Name: "Dani", Age: 36}, expected: "Nome: Dani - Idade: 36\n"}, } for _, tc := range tt { data, err := json.Marshal(tc.rVar) if err != nil { t.Fatal(err) } req, err := http.NewRequest("POST", "/person", bytes.NewBuffer(data)) if err != nil { t.Fatal(err) } rr := httptest.NewRecorder() router := mux.NewRouter() router.HandleFunc("/person", HandlePerson).Methods("POST") router.ServeHTTP(rr, req) if rr.Body.String() != tc.expected { t.Errorf("wrong response body for param %v: got %v want %v", tc.rVar, rr.Body.String(), tc.expected) } } }
Ao executar go test ./handlers -v
novamente, devemos ter o seguinte output.
=== RUN TestHandleHello
--- PASS: TestHandleHello (0.00s)
=== RUN TestHandlePerson
--- PASS: TestHandlePerson (0.00s)
PASS
ok github.com/aprendagolang/gorilla/handlers
Deixem suas dúvidas nos comentários.
Até a próxima!