A logo showing the text blog.marcnuri.com
English
Inicio»Go»Pruebas unitarias para APIs REST basadas en Go Gin Web Framework con httptest

Entradas Recientes

  • Fabric8 Kubernetes Client 7.2.0 está disponible!
  • Conectarse a un servidor MCP con JavaScript y AI SDK
  • Conectarse a un servidor MCP con JavaScript y LangChain.js
  • El Futuro de las Herramientas para Desarrolladores en la era de la IA
  • Conectarse a un servidor Model Context Protocol (MCP) con Java y LangChain4j

Categorías

  • Antiguo
  • Front-end
  • Go
  • Herramientas
  • Industria y negocios
  • Inteligencia Artificial
  • Java
  • JavaScript
  • Operaciones
  • Personal
  • Proyectos personales

Archivos

  • mayo 2025
  • abril 2025
  • marzo 2025
  • febrero 2025
  • enero 2025
  • diciembre 2024
  • noviembre 2024
  • agosto 2024
  • junio 2024
  • mayo 2024
  • abril 2024
  • marzo 2024
  • febrero 2024
  • enero 2024
  • diciembre 2023
  • noviembre 2023
  • octubre 2023
  • septiembre 2023
  • agosto 2023
  • julio 2023
  • junio 2023
  • mayo 2023
  • abril 2023
  • marzo 2023
  • febrero 2023
  • enero 2023
  • diciembre 2022
  • noviembre 2022
  • octubre 2022
  • agosto 2022
  • julio 2022
  • mayo 2022
  • marzo 2022
  • febrero 2022
  • enero 2022
  • diciembre 2021
  • noviembre 2021
  • octubre 2021
  • septiembre 2021
  • agosto 2021
  • julio 2021
  • diciembre 2020
  • octubre 2020
  • agosto 2020
  • junio 2020
  • mayo 2020
  • marzo 2020
  • febrero 2020
  • enero 2020
  • noviembre 2019
  • octubre 2019
  • julio 2019
  • diciembre 2018
  • agosto 2018
  • julio 2018
  • junio 2018
  • mayo 2018
  • marzo 2018
  • febrero 2018
  • noviembre 2017
  • octubre 2017
  • agosto 2017
  • julio 2017
  • enero 2017
  • julio 2016
  • enero 2016
  • diciembre 2015
  • noviembre 2015
  • diciembre 2014
  • marzo 2014
  • febrero 2011
  • junio 2008
  • mayo 2008
  • abril 2008
  • enero 2008
  • junio 2007
  • mayo 2007
  • abril 2007
  • marzo 2007

Pruebas unitarias para APIs REST basadas en Go Gin Web Framework con httptest

2023-10-25 en Go etiquetado Go / Gin Web Framework / Testing / httptest / Test-Driven Development (TDD) por Marc Nuri | Última actualización: 2023-10-25
English version

Introducción

Gin Web Framework es uno de los frameworks más populares de Go para construir APIs RESTful. Cuando implementas tus endpoints REST o HTTP, especialmente si sigues un enfoque de desarrollo guiado por pruebas (TDD), lo más probable es que quieras escribir pruebas unitarias para verificar tus endpoints.

El paquete net/http/httptest es una librería estándar que proporciona utilidades para probar servidores y clientes HTTP.

En este artículo, te mostraré cómo puedes probar tus APIs RESTful basadas en Gin-Gonic usando el paquete httptest para probar las peticiones y respuestas HTTP.

El API probado

Para poder mostrar las capacidades de pruebas del paquete httptest, he implementado una aplicación API RESTful sencilla usando Gin. La aplicación expone un único endpoint en la ruta raíz / que permite operaciones de creación, lectura, actualización y eliminación (CRUD) para un servicio de almacenamiento de objetos multipropósito ficticio. Este es un proyecto de demostración sencillo, los objetos manejados por la API se almacenan en memoria y no se persisten.

Los endpoints expuestos por la aplicación son los siguientes:

  • GET /:
    • Devuelve una lista de todos los objetos del servicio para peticiones Accept: application/json
    • Devuelve el nombre de la aplicación en cualquier otro caso
  • POST /: Crea un nuevo objeto y le asigna un nuevo id
  • PUT /:id: Actualiza o inserta el objeto facilitado para el id proporcionado
  • DELETE /:id: Elimina el objeto con el id proporcionado

El siguiente fragmento de código muestra la función de configuración del router Gin:

router.go
func SetupRouter() *gin.Engine {
  router := gin.Default()
  router.GET("/", addCommonHeaders, get, fallbackGet)
  router.POST("/", addCommonHeaders, post)
  router.PUT("/:id", addCommonHeaders, put)
  router.DELETE("/:id", addCommonHeaders, remove)
  return router
}

Puedes encontrar la implementación completa del router en GitHub (router.go).

La función SetupRouter se usa posteriormente por la función main de la aplicación para iniciar el servidor:

main.go
err := router.SetupRouter().Run("0.0.0.0:8080")

No obstante, la función SetupRouter también se usará por las pruebas para registrar las respuestas del router. Veámoslo a continuación.

Cómo escribir una prueba sencilla

El siguiente fragmento de código muestra el test completo para el endpoint GET / cuando se trata una petición sin Accept header. En este caso, la aplicación debería devolver un código de estado 200, el nombre de la aplicación en el cuerpo y algunas cabeceras estándar.

router_test.go
func TestFallbackGet(t *testing.T) {
  router := SetupRouter()
  recorder := httptest.NewRecorder()
  router.ServeHTTP(recorder, httptest.NewRequest("GET", "/", nil))
  t.Run("Returns 200 status code", func(t *testing.T) {
    if recorder.Code != 200 {
      t.Error("Expected 200, got ", recorder.Code)
    }
  })
  t.Run("Returns app name", func(t *testing.T) {
    if recorder.Body.String() != "\"Cocktail service\"" {
      t.Error("Expected '\"Cocktail service\"', got ", recorder.Body.String())
    }
  })
  t.Run("Returns Server header", func(t *testing.T) {
    if recorder.Header().Get("Server") != "gin-gonic/1.33.7" {
      t.Error("Expected 'gin-gonic/1.33.7', got ", recorder.Header().Get("Server"))
    }
  })
}

Comenzamos la implementación del test creando un nuevo router Gin invocando la función SetupRouter de la aplicación. A continuación, creamos un nuevo httptest.Recorder. Lo emplearemos para capturar la respuesta del router. Por último, invocamos la función ServeHTTP del router pasándole el recorder y una nueva httptest.Request con la ruta GET / y sin cuerpo. Esta es la sección de Acción de la prueba, que consta de dos partes:

Generando la petición usando httptest.NewRequest

httptest.NewRequest("GET", "/", nil)

El primer argumento es el método HTTP, el segundo es la ruta y el tercero es el cuerpo de la petición.

Invocando la función ServeHTTP del router

router.ServeHTTP(recorder, request)

El primer argumento es el recorder que creamos previamente y el segundo es la petición.

La implementación del test continúa con múltiples subtests para verificar las diferentes partes de la respuesta. Veamos cómo.

Verificando el código de estado

La primera verificación en el test TestFallbackGet se asegura de que el código de estado devuelto por el router es 200.

Para ello, comprobamos el valor de la propiedad recorder.Code que contiene el código de estado HTTP de la respuesta. Debido a que no estamos usando ninguna librería o framework de testing, comparamos manualmente el valor con el esperado.

router_test.go
if recorder.Code != 200 {
  t.Error("Expected 200, got ", recorder.Code)
}

Si la verificación falla, usamos la función t.Error para reportar el error.

Verificando el cuerpo de la respuesta

La siguiente comprobación en el test TestFallbackGet verifica que el cuerpo de la respuesta del servidor contiene el nombre de la aplicación (Cocktail service).

Como se trata de un literal de texto plano, podemos usar la función recorder.Body.String() para obtener el cuerpo de la respuesta como una cadena/string.

router_test.go
if recorder.Body.String() != "\"Cocktail service\"" {
  t.Error("Expected '\"Cocktail service\"', got ", recorder.Body.String())
}

A continuación, comparamos el valor con el esperado de la misma forma que hicimos con el código de estado y reportamos el error si la comprobación falla.

Verificando las cabeceras de la respuesta

La última verificación en el test TestFallbackGet comprueba que la respuesta del servidor contiene la cabecera/header Server con el motor y la versión de la aplicación.

Esta cabecera la establecemos manualmente en la función addCommonHeaders de la aplicación. La función addCommonHeaders es un handler que se añade a todas las rutas de la aplicación para establecer algunas cabeceras de respuesta que son comunes a todos los endpoints:

router.go
func addCommonHeaders(c *gin.Context) {
  c.Header("Cache-Control", "no-cache, no-store")
  c.Header("Server", "gin-gonic/1.33.7")
}

Para comprobar que la cabecera Server está presente en la respuesta, usamos la función recorder.Header().Get("Server") para obtener su valor.

router_test.go
if recorder.Header().Get("Server") != "gin-gonic/1.33.7" {
  t.Error("Expected 'gin-gonic/1.33.7', got ", recorder.Header().Get("Server"))
}

A continuación, comparamos el valor con el esperado de la misma forma que hicimos con el código de estado y el cuerpo de la respuesta y reportamos el error si la comprobación falla.

Estos escenarios para verificar los componentes básicos de la respuesta son muy sencillos. Sin embargo, podemos usar el mismo enfoque para probar escenarios más complejos. Veamos ahora cómo probar escenarios más complejos para verificar el comportamiento completo de nuestra API RESTful.

Probando escenarios más complejos

El test TestFallbackGet que hemos visto en la sección anterior es una prueba muy sencilla que verifica los componentes básicos de la respuesta para una petición HTTP GET. No obstante, podemos usar el mismo enfoque para probar escenarios más complejos como diferentes métodos HTTP, cabeceras, cuerpos de petición, cuerpos de respuesta, etc.

Creando una petición

La aplicación probada tiene un único endpoint que admite varios métodos HTTP. Uno de los métodos aceptados es POST. Al realizar una petición POST, la aplicación espera un cuerpo JSON que contenga un objeto y también una cabecera con el Content-Type establecido a application/json.

Podemos usar el paquete httptest para crear una petición con estos requisitos de forma fácil:

request := httptest.NewRequest("POST", "/", strings.NewReader(`{
	"name": "Bloody Mary",
	"rating": 1
}`))
request.Header.Add("Content-Type", "application/json")

En la primera línea, creamos una nueva httptest.Request con un método POST a la ruta / y un cuerpo JSON que contiene un objeto. Para crear el cuerpo, usamos la función strings.NewReader para crear un nuevo io.Reader a partir de un literal de cadena/string. El literal de cadena/string contiene la representación JSON del objeto que queremos enviar al servidor.

La siguiente línea añade la cabecera Content-Type a la petición usando la función Request.Header.Add.

Esta petición ahora puede usarse para invocar la función ServeHTTP del router y registrar la respuesta.

router.ServeHTTP(recorder, request)

Verificando el cuerpo de una respuesta JSON

Cuando realizamos una petición POST con un cuerpo JSON que contiene un objeto, la aplicación asigna automáticamente un id al objeto y lo devuelve en el cuerpo de la respuesta. Este y otros escenarios se prueban en el test TestPostValid.

El siguiente fragmento de código muestra el subtest que verifica que el objeto devuelto contiene el id generado:

router_test.go
t.Run("Returns saved object with id", testCase(func(t *testing.T, c *context) {
  c.router.ServeHTTP(c.recorder, reqBuilder())
  var body map[string]interface{}
  json.Unmarshal(c.recorder.Body.Bytes(), &body)
  matched, _ := regexp.MatchString(
    "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",
    body["id"].(string),
  )
  if !matched {
    t.Error("Expected object with id, got ", body)
  }
}))

En este caso, usamos la función json.Unmarshal para procesar el cuerpo de la respuesta y guardarlo en un map[string]interface{}. Luego, usamos una expresión regular para verificar que la propiedad id del objeto coincide con el formato de un UUID. Para ello, usamos la función regexp.MatchString para realizar la comprobación de la expresión regular.

El fichero router_test.go contiene una suite de pruebas completa que verifica todo el comportamiento de la aplicación (además de ofrecer un 100% de cobertura de código). Por favor, asegúrate de revisarla.

Conclusión

En este artículo, hemos visto cómo probar una API RESTful basada en Gin Web Framework usando el paquete net/http/httptest de Go. Hemos comenzado definiendo el comportamiento de la aplicación. Luego, hemos implementado algunas pruebas para verificar los diferentes componentes de la respuesta a una petición HTTP GET sencilla. Por último, hemos visto cómo probar escenarios más complejos como diferentes métodos HTTP, cabeceras, cuerpos de petición, cuerpos de respuesta, etc. Siguiendo estas prácticas de pruebas, puedes asegurar la robustez y fiabilidad de tus APIs.

Puedes encontrar el código fuente del artículo en GitHub.

¡Feliz testing!

Twitter iconFacebook iconLinkedIn iconPinterest iconEmail icon

Navegador de artículos
Introducción a Testcontainers para GoCómo preparar y desmontar tests unitarios en Go
© 2007 - 2025 Marc Nuri