No tienes acceso a esta clase

¡Continúa aprendiendo! Únete y comienza a potenciar tu carrera

Implementando Mocks

21/30
Recursos

Aportes 10

Preguntas 7

Ordenar por:

¿Quieres ver más aportes, preguntas y respuestas de la comunidad?

Para los que se preguntan porque hay tantos “errores” (lo rojo) en el codigo como se ve en esta parte:

Es porque el language server (gopls) no sabe como relacionar main.go y main_test.go porque no hacen parte de un go module. La “solucion” mas sencilla es recargar el language server. Si no, solo creen un go module:

$ go mod init testingmod

Me quedaron algunas dudas respecto a este codigo, ya que no entedia muy bien como es que se reasignaban las funciones de “GetEmployeeById” y “GetPersonByDNI” si estaban fuera del scope de “mockFunc”

Así que decidí preguntarle al chat GPT y me aclaró muchas dudas, les comparto mi conversación traducida por si a alguien le puede servir a aclarar un poco mas el tema


YO:

Estoy trabajando con el siguiente código en go
[le adjunté todo nuestro codigo]
pero no entiendo muy bien lo que está haciendo, ¿podrías explicármelo?

GPT:

Este código es un caso de prueba para una función llamada GetFullTimeEmployeeById. El caso de prueba define una tabla de entradas y salidas esperadas de prueba, y luego recorre cada prueba en la tabla, ejecutando la función GetFullTimeEmployeeById con las entradas de la prueba y comparando su salida con la salida esperada.

Antes de ejecutar cada prueba, el código reasigna las funciones globales GetEmployeeById y GetPersonByDNI a funciones anónimas que devuelven valores codificados. Esto se hace para que la función GetFullTimeEmployeeById siempre devuelva la misma salida para una prueba dada, independientemente del estado actual del sistema.

El código también comprueba si ocurrió un error durante la ejecución de la prueba, y si se encuentra un error, la prueba lo registra
Al final de la prueba, el código reasigna las funciones originales GetEmployeeById y GetPersonByDNI a las variables globales, para asegurarse de que la prueba no interfiera con otras partes del código que puedan estar utilizando estas funciones.

YO:

Gracias, pero todavía no entiendo cómo puede reasignar globalmente las funciones “GetEmployeeById” y “GetPersonByDNI” en el “mockFunc”, ¿no se supone que el codigo reasigna las funciones sólo mientras están dentro de las llaves de “mockFunk”?

GPT:

Tienes razón en que, por defecto, la reasignación de las funciones GetEmployeeById y GetPersonByDNI dentro de la función mockFunc sólo duraría lo que dure la llamada a la función. Sin embargo, dado que estas funciones son variables globales, la reasignación dentro de la función mockFunc está cambiando efectivamente el valor de la variable global, y no sólo el valor de la variable local con el mismo nombre. Por lo tanto, los cambios en estas variables globales persisten fuera del ámbito de la función mockFunc.

También vale la pena señalar que, mientras reasigna las funciones globales dentro de mockFunc a funciones anónimas que devuelven valores codificados, también almacena las funciones originales en variables denominadas originalGetEmployeeById y originalGetPersonByDNI respectivamente.
Esto se hace para que el código pueda restaurar las funciones originales una vez finalizado el caso de prueba, para evitar afectar al estado del sistema una vez finalizado el caso de prueba.

YO:

De acuerdo, ahora lo entiendo mejor, pero tengo otra pregunta, ¿por qué las funciones almacenadas en las variables “originalGetEmployeeById” y “originalGetPersonByDNI” no se ven afectadas cuando “mockFunc” hace el cambio, si esas variables son las mismas funciones no se supone que deben cambiar también?

GPT:

Tienes razón en que originalGetEmployeeById y originalGetPersonByDNI almacenan los valores de las variables globales GetEmployeeById y GetPersonByDNI en el momento en que se asignan por primera vez. Sin embargo, cuando se llama a la función mockFunc y ésta reasigna las variables globales GetEmployeeById y GetPersonByDNI, no cambia los valores almacenados en las variables originalGetEmployeeById y originalGetPersonByDNI. Estas son dos variables separadas, con diferentes valores almacenados en ellas.

Es importante entender que las variables en Go se pasan por valor, lo que significa que cuando asignas una variable a otra, se almacena una copia del valor en la nueva variable, no una referencia al valor original. Así, cuando a originalGetEmployeeById y originalGetPersonByDNI se les asigna el valor de GetEmployeeById y GetPersonByDNI, se les da una copia de los valores de esas variables, no una referencia a las variables mismas. Por lo tanto, cuando GetEmployeeById y GetPersonByDNI se reasignan en el mockFunc, esto no afecta a los valores almacenados en originalGetEmployeeById y originalGetPersonByDNI.

Hay una librería llamada Mockery, que te genera los Mocks automáticamente utilizando un comando.

Una vez te genera un mock, es llegar y utilizarlo. Muy fácil y practico.
Dejo el proyecto para que le echen una mirada:
https://github.com/vektra/mockery

Hola gente, aquí les dejo mis apuntes del Apartado de testing con golang, por si quieren revisarlo jeje
https://plump-flare-484.notion.site/Testing-c945f250509e449d8b9dee247bdc63a6

package main

import "testing"

func TestGetFullTimeEmployeeById(t *testing.T) {
	table := []struct {
		id               int
		dni              string
		mockFunc         func()
		expectedEmployee FullTimeEmployee
	}{
		{
			id:  1,
			dni: "12345678",
			mockFunc: func() {
				GetEmployeeByID = func(id int) (Employee, error) {
					return Employee{
						Id:       1,
						Position: "CEO",
					}, nil
				}

				GetPersonByDNI = func(dni string) (Person, error) {
					return Person{
						Name: "John Doe",
						Age:  35,
						DNI:  "12345678",
					}, nil
				}
			},
			expectedEmployee: FullTimeEmployee{
				Person: Person{
					Age:  35,
					DNI:  "12345678",
					Name: "John Doe",
				},
				Employee: Employee{
					Id:       1,
					Position: "CEO",
				},
			},
		},
	}

	// Save the original functions
	originalGetEmployeeByID := GetEmployeeByID
	originalGetPersonByDNI := GetPersonByDNI

	for _, test := range table {
		// Set the mock functions for the test
		test.mockFunc()

		// Call the function to test
		ft, err := GetFullTimeEmployeeById(test.id, test.dni)
		if err != nil {
			t.Errorf("Error getting full time employee by id: %v", err)
		}

		// Check if the result is the expected
		if ft.Age != test.expectedEmployee.Age {
			t.Errorf("Expected age %v, got %v", test.expectedEmployee.Person.Age, ft.Person.Age)
		}

		if ft.DNI != test.expectedEmployee.DNI {
			t.Errorf("Expected dni %v, got %v", test.expectedEmployee.Person.DNI, ft.Person.DNI)
		}

		if ft.Name != test.expectedEmployee.Name {
			t.Errorf("Expected name %v, got %v", test.expectedEmployee.Person.Name, ft.Person.Name)
		}

		if ft.Employee.Id != test.expectedEmployee.Employee.Id {
			t.Errorf("Expected employee id %v, got %v", test.expectedEmployee.Employee.Id, ft.Employee.Id)
		}

		if ft.Employee.Position != test.expectedEmployee.Employee.Position {
			t.Errorf("Expected employee position %v, got %v", test.expectedEmployee.Employee.Position, ft.Employee.Position)
		}
	}

	// Restore the original functions
	GetEmployeeByID = originalGetEmployeeByID
	GetPersonByDNI = originalGetPersonByDNI
}

También se puede hacer uso de

reflect.DeepEqual()

Para comparar objetos sin tener que ir propiedad por propiedad, en este caso quedaría así la única condicional:

if !reflect.DeepEqual(ft, test.expectedEmployee) {
	t.Errorf("Not equal employees, \nExpected: %v\nGot: %v" , ft , test.expectedEmployee)
}
Una consulta, por que mejor no usamos interfaces y que luego se implementen la firma de dichos metodos en el main y posteriormente en el test? asi evitamos estar declarando funciones anonimas o corriendo el riesgo de que una funcion mockeada reemplace a la original
Basicamente una manera de no esperar la ejecucion de una funcion para que te devuelva su valor si no devolver los valores de manera directa para ahorrar tiempo.

Se entiende el concepto de poder mockear, reemplazando la implementación y restaurándola al final. Recomendaría evitar este método, dado que la implementación se puede reemplazar en un test, como en el programa original.

Además, el programador se puede olvidar fácilmente de hacer alguna restauración y traiga consecuencias inesperadas en el resultado de las pruebas unitarias.

En otras aportaciones se comentaron herramientas para generar mocks de forma automatizada, esto es una mejor solución. Agrego que es preferible evitar los mocks y los efectos secundarios cuando sea posible, hace que el comportamiento del código más predecible.

Si quieren mantener las funciones asi: func GetEmployeeById(id *int*) (Employee, *error*) { *//time.Sleep(5 \* time.Second)* *// This is like simulatating a query to a database* *return* Employee{ id: id, }, nil} func GetPersonByDNI(dni *string*) (Person, *error*) { *//time.Sleep(5 \* time.Second)* *return* Person{ dni: dni, }, nil} Pueden hacer el test de esta forma usando la libreria de testify/mock: package main *import* ( "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock") type PersonMock struct { mock.Mock} type EmployeeMock struct { mock.Mock} func (m \*EmployeeMock) GetEmployeeById(id *int*) (Employee, *error*) { args := m.Called(id) *return* args.Get(0).(Employee), args.Error(1)} func (m \*PersonMock) GetPersonByDNI(dni *string*) (Person, *error*) { args := m.Called(dni) *return* args.Get(0).(Person), args.Error(1)} func TestGetFullTimeEmployeeById\_Success(t \*testing.T) { *// Arrange* expectedEmployee := FullEmployee{ Person: Person{dni: "12345678A"}, Employee: Employee{id: 1}, } *// Mocks* mockEmployeeDB := new(EmployeeMock) mockEmployeeDB.On("GetEmployeeById", 1).Return(Employee{id: 1}, nil) mockPersonDB := new(PersonMock) mockPersonDB.On("GetPersonByDNI", "12345678A").Return(Person{dni: "12345678A"}, nil) *// Act* result, err := GetFullTimeEmployeeById(1, "12345678A") *// Assert* assert.NoError(t, err) assert.Equal(t, expectedEmployee, result)} Lo que pasa es que esas funciones GetPersonByDNI y getEmployeeById, estaban enviando un struct vacio, con esa correcion pueden ver otra forma de ver la implemntacion con testify/mock