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
ResponseRecordere aRequestpara 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!
Faça parte da comunidade!
Receba os melhores conteúdos sobre Go, Kubernetes, arquitetura de software, Cloud e esteja sempre atualizado com as tendências e práticas do mercado.
Livros Recomendados
Abaixo listei alguns dos melhores livros que já li sobre GO.

