Contenido del curso

Mocks en Go para testear repositorios

Resumen

Testear código que toca una base de datos sin levantar una base de datos real es uno de esos retos que separan un proyecto frágil de uno robusto. Con mocks en Go usando Testify, puedes simular las conexiones, validar la lógica de tus repositorios y blindar tu aplicación frente a errores y brechas de seguridad, todo sin depender de infraestructura externa.

¿Por qué los mocks importan para la seguridad de una aplicación en Go?

Una aplicación fácil de testear es una aplicación fácil de auditar. Cuando puedes reproducir escenarios de fallo en segundos, también puedes encontrar bugs y brechas antes que un atacante.

Lo contrario también aplica: si tu código es difícil de testear, será difícil de cambiar y aún más difícil de proteger. Esa fricción es exactamente donde se cuelan los riesgos de seguridad.

¿Qué es un mock en Go? Es una implementación falsa de una interfaz que simula el comportamiento de una dependencia real, como una base de datos, para poder testear lógica de negocio de forma aislada.

¿Cómo se instala Testify y su submódulo Mock?

El punto de partida es traer la dependencia al proyecto. Testify expone un submódulo llamado mock pensado justo para este caso de uso [01:05].

  • Ejecuta go get apuntando a github.com/stretchr/testify/mock.
  • Verifica que el módulo quede registrado en tu go.mod.
  • Importa el paquete en el archivo donde construirás el mock.

Con eso ya tienes la base para implementar dobles de prueba sobre cualquier interfaz de tu repositorio.

¿Cómo implementar un mock de un repositorio de commits?

La clave está en recordar que el repositorio expone una interfaz Commit, no una implementación concreta. Como los métodos consumen esa interfaz, basta con crear otra implementación que también la cumpla y usarla en los tests [01:35].

Estructura base con mock.Mock embebido

Dentro de la carpeta repository creamos un archivo commits_mock.go con el mismo paquete. Allí defines una estructura MockCommitStruct que embebe un puntero a mock.Mock, el tipo que Testify usa para registrar llamadas y respuestas.

Ese embed es lo que te da acceso a métodos como Called y On, fundamentales para describir qué debe pasar cuando alguien invoque tu mock.

Implementación de los métodos de la interfaz

Para cumplir la interfaz Commit replicamos sus métodos sobre MockCommitStruct. La mecánica es la misma en cada uno:

  • Llamas a m.Called(...) con los argumentos recibidos para registrar la invocación.
  • Recoges los valores configurados en el test mediante args.Get(0), args.Error(1), etc.
  • Retornas exactamente lo que el test definió.

En el método de inserción solo retornas el error en la posición cero. En el método que busca por email, retornas la entity Commit desde results.Get(0) y el error desde results.Error(1). Así puedes simular tanto el camino feliz como los fallos de base de datos [02:55].

¿Para qué sirve mock.Called en Testify? Registra la llamada al método con sus argumentos para que después puedas hacer assertions sobre si fue invocado y con qué parámetros, además de devolver los valores configurados.

¿Cómo escribir un método testeable que procese un webhook de GitHub?

Una vez listo el mock, el siguiente paso es construir lógica que lo aproveche. Creamos InsertGithubWebhook, una función que recibe un context.Context, el repositorio repository.Commit y un models.GithubWebhook, y retorna un error [04:05].

Notarás un detalle importante: el modelo se renombra de PushEvent a GithubWebhook para que el nombre comunique mejor su intención. Pequeñas decisiones de naming como esta hacen el código más legible y, de paso, más fácil de revisar en una auditoría.

Construcción de la entity Commit desde el webhook

Dentro de la función mapeamos los datos del webhook a una entity.Commit. La idea es que cada campo de la entidad provenga de una fuente clara dentro del payload:

  • Repo se toma de webhook.Repository.FullName.
  • CommitID, Message, AuthorUsername y AuthorEmail salen de webhook.HeadCommit.
  • Payload se recibe aparte como body string, porque representa el cuerpo crudo del webhook.
  • CreatedAt y UpdatedAt se setean con time.Now() usando la librería nativa time.

El ID no se asigna manualmente porque será autogenerado por la base de datos. Ese tipo de decisiones evita colisiones y mantiene la responsabilidad donde corresponde.

Por qué este diseño es fácil de testear

Al recibir solo context, el repositorio como interfaz y el webhook como struct, puedes inyectar el MockCommitStruct en lugar del repositorio real. Eso te permite probar:

  • Que la entity construida tenga los campos correctos.
  • Que el método Insert se llame con el contexto y el commit esperados.
  • Que un error simulado de base de datos se propague correctamente.

Todo sin abrir una conexión real, lo que acelera el ciclo de feedback y reduce flakiness en CI.

¿Qué ganas al combinar interfaces, mocks y entidades bien tipadas?

Ganas tres cosas al mismo tiempo: tests rápidos, código desacoplado y una superficie de ataque más pequeña. Cuando cada dependencia externa pasa por una interfaz, puedes inspeccionarla, simularla y cambiarla sin reescribir tu lógica de negocio.

Si ya tienes este patrón en tu proyecto, cuéntame en los comentarios qué método fue el primero que migraste a mocks y qué bug descubriste en el camino.