# Escribir tests

Las pruebas pueden escribirse de diversas maneras. Esta sección intenta ofrecer un estándar y una justificación detrás de la forma en que escribimos pruebas.

Las pruebas DEBEN escribirse usando el **describe** y el **it** métodos provistos por jest

## Describing and building context

El **describe** el método DEBERÍA utilizarse para describir, siempre que sea posible, el contexto en el que se ejecutará el fragmento de código a probar y DEBERÍA construir ese contexto dentro de su propio ámbito.

```tsx
describe('when the flag is red', () => {
	...
})

describe('when the flag is blue', () => {
	...
})
```

Para describir el contexto, DEBERÍAMOS usar una de las siguientes palabras: **when** para describes de nivel superior y **having** o **y** para indicar que hay un contexto que depende de otro contexto definido anteriormente. La razón detrás de esto es tener contextos auto-descriptivos que puedan entenderse y comprobarse fácilmente combinando las frases describe. Dado que la idea es concatenar las descripciones de los contextos, **describes** DEBEN escribirse en minúsculas.

```tsx
describe('when the flag is red', () => {
	...
	describe('and the wind is strong', () => {
     // El contexto aquí está descrito por la conjunción de los describes:
     // "when the red flag is red and the wind is strong"
     ...
  })
})
```

Múltiples describes pueden estar anidados al mismo nivel y en niveles inferiores. Los describes en el mismo nivel denotan contextos diferentes y los describes en niveles inferiores (describes anidados) denotan un contexto más específico.

```tsx
describe('when the flag is red', () => {
	...
	describe('and the wind is strong', () => {
     ...
  })

	describe('and the wind is weak', () => {
     ...
  })
})
```

Como se mencionó antes, cada describe DEBERÍA ir acompañado de un método que construya el contexto o un método que destruya o cambie el contexto. Los métodos que DEBEN usarse son el **beforeEach** y el **afterEach**.

{% hint style="warning" %}
El framework Jest tiene dos métodos, **beforeEach** y **beforeAll**, pero combinar ambos genera confusión y problemas de ejecución cuando hay contextos anidados, ya que [el orden en el que se ejecutan](https://jestjs.io/docs/setup-teardown#scoping) no es lo que esperaríamos.
{% endhint %}

Contextos creados con el **beforeEach** método se irán definiendo cada vez más a medida que profundizamos en el árbol de describe. Cada **beforeEach** especificará el contexto más de acuerdo con lo que su texto indica.

### Ámbito de variables

El **variables** definidas en los contextos DEBERÍAN declararse de acuerdo con el alcance en el que se van a usar. Es decir, si una variable se usa solo para un contexto específico en un nivel anidado de describes, la variable DEBERÍA definirse únicamente en ese contexto específico. La razón detrás de esto es mantener las variables cerca del código en el que se usan, facilitando la comprensión del código.

```tsx
let flag = 'blue'
describe('when the flag is red', () => {
	let wind = 'mild'
	beforeEach(() => {
    flag = 'red'
  })
  ...

	describe('and the wind is strong', () => {
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('and the wind is weak', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
```

Variables de **tipos primitivos**, que **no serán cambiadas** en el contexto en el que se definen o en contextos posteriores DEBERÍAN definirse como **const** una vez, sobre la **beforeEach** definición en el ámbito.

Variables de **tipos primitivos** que serán cambiados en el contexto en el que se definen o en contextos posteriores DEBEN definirse como **let**, según obliga la naturaleza de Typescript.

Variables de **tipos no primitivos** (objetos, arrays, etc.) DEBEN definirse como **let** y su valor DEBE establecerse en un **beforeEach.** La razón detrás de esto es que los objetos en JS son mutables, lo que significa que cualquier ejecución de código que use ese objeto está sujeta a cambios y puede afectar otras pruebas de forma involuntaria.

```tsx
let flag
describe('when the flag is red', () => {
  // Un valor primitivo que va a ser modificado en múltiples ámbitos.
	let wind
  // Un valor objeto que va a ser usado en el código de la prueba.
  let someObject
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1 }
  })
  ...

	describe('and the wind is strong', () => {
		// Un valor primitivo constante que se va a usar solo en este ámbito.
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('and the wind is weak', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
...
```

Las variables DEBERÍAN estar correctamente tipadas siempre que sea posible. Los tipos repetidos DEBERÍAN abstraerse en un tipo.

```tsx
let flag: string
describe('when the flag is red', () => {
  // Un valor primitivo que va a ser modificado en múltiples ámbitos.
	let wind: string
  // Un valor objeto que va a ser usado en el código de la prueba.
  let someObject: { id: string }
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1 }
  })
  ...

	describe('and the wind is strong', () => {
		// Un valor primitivo constante que se va a usar solo en este ámbito.
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    ...
  })

	describe('and the wind is weak', () => {
		beforeEach(() => {
	    wind = 'weak'
	  })
    ...
  })
})
...
```

## Describiendo expectativas y ejecutando código

El **it** el método DEBE colocarse siempre dentro del ámbito de un **describe** y DEBE usarse para describir qué esperamos del código que vamos a probar y DEBERÍA contener, si es posible, solo **una** aserción. Pueden existir múltiples aserciones por **it** si hay un problema de rendimiento, ya que las pruebas se ejecutarán una vez por **it** (debido al **beforeEach**), o si lo esperado puede describirse con claridad en la descripción de la expectativa.

```tsx
let flag: string
describe('when the flag is red', () => {
	let wind: string
  let someObject: { id: string }
	beforeEach(() => {
    flag = 'red'
		someObject = { id: 1, swim: jest.fn() }
  })
  ...

  // Expectativa única de la ejecución de una prueba en un contexto (un it)
	describe('and the wind is strong', () => {
		const aConstantPrimitiveValue = 'something'
		beforeEach(() => {
	    wind = 'strong'
	  })
    
		it('should not go swimming', () => {
      expect(goSwimming(flag, wind, someObject)).toBe(false)
    })
  })

  // Múltiples expectativas de la ejecución de una prueba en un contexto (dos its)
	describe('and the wind is weak', () => {
    let result: boolean
		beforeEach(() => {
	    wind = 'weak'
      result = goSwimming(flag, wind, someObject)
	  })

		it('should go swimming', () => {
      expect(goSwimming(flag, wind, someObject)).toBe(true)
    })

    it('should have called the swim method', () => {
      expect(someObject.swim).toHaveBeenCalled()
    })
  })
})
...
```

La razón detrás de esta estructura es proporcionar claridad al revisor de las pruebas y a los desarrolladores que mantendrán y harán cambios en el código, ya que cada contexto y expectativa está claramente enumerado, lo que facilita comprender qué se está probando, cómo se prueba y qué queda por probar.

Siguiendo los **describe** descripciones hasta los **it**s los desarrolladores pueden entender fácilmente qué se está probando concatenando las frases.

```tsx
// Esto se lee como: "when the flag is red and the wind is strong, 
// debería ir a nadar"
describe('when the flag is red', () => {
  ...
  describe('and the wind is strong', () => {
		...
		it('should go swimming', () => {
			...
		})
  })
})
```

### Escribir expectativas claras

La descripción de las expectativas escritas en los **it**s DEBE ser lo más descriptiva posible sobre lo que se espera de la ejecución de las pruebas. Los desarrolladores NO DEBEN usar formulaciones abstractas o generales para definir expectativas, ya que quitan claridad sobre lo que se espera del código.

{% hint style="danger" %}
**El desarrollador NO DEBE usar frases como:**

* "should work as expected" ⇒ ¿Qué debe funcionar como se espera?
* "should return the correct value" ⇒ ¿Cuál es el valor correcto?
* "should resolve/return/work correctly" ⇒ ¿Cómo funciona algo correctamente?
* "should fail" ⇒ ¿Cómo debería fallar? ¿Cuál es el mensaje que debería proporcionar?
  {% endhint %}

Debe tenerse en cuenta que al especificar lo que se espera en los **it**s o cuál será el contexto en los **describe**s los desarrolladores PUEDEN usar nombres de funciones o mensajes de error exactos si se requiere para entender mejor la intención, pero DEBERÍAN usar una representación textual cuando sea posible para facilitar el mantenimiento de las pruebas.

```tsx
// math.ts
export function div(a: number, b: number): number {
	if(b === 0)	{
		throw new Error('The divisor b equals 0, the division can\'t be performed')
	}
}

// test.spec.ts
import { div } from './math.ts'

describe('when dividing by zero', () => {
  // La descripción del it describe la intención del mensaje de la excepción
	it('should throw an exception signaling that a division by 0 is not possible', () => {
		expect(() => div(12, 0)).toThrowError('The divisor b equals 0, the division can\'t be performed')
	})
})
```

## Qué probar

Elegir qué probar puede variar según el código que se esté ejecutando. Diferentes factores, como el rendimiento o un gran dominio de entradas, pueden cambiar definitivamente lo que se debe o no probar. Aquí expondremos un único conjunto de casos que el desarrollador DEBERÍA probar si es posible.

```tsx
function run(kilometers: number): number {
	if(kilometers > 1000) {
    throw new Error('The runner can\'t run more than 1000 kilometers')
  }

  if(kilometers <= 10) {
		return kilometers
  } else if(kilometers > 10) {
		doSomething(kilometers)
    return beLazy(kilometers)
  }
}
```

La función run aquí tiene un par de flujos de ejecución diferentes. Las funciones, o secciones de código, NO DEBERÍAN probarse basándose únicamente en los diferentes flujos de ejecución posibles, sino en lo que se espera de la función, ya que la función podría no estar haciendo aquello para lo que fue escrita y las pruebas podrían quedar fuertemente acopladas al código y no detectar distintos problemas. Esto implica que el desarrollador DEBERÍA siempre probar todos los posibles caminos de ejecución y DEBERÍA probar otros caminos posibles también de acuerdo con la semántica de la función.

Para este caso específico, el desarrollador DEBERÍA probar **al menos** los siguientes casos:

1. Llamar a la función run con una cantidad de kilómetros mayor que 1000
2. Llamar a la función run con una cantidad de kilómetros igual a 10
3. Llamar a la función run con una cantidad de kilómetros mayor que 10 pero menor que 1000

Para el primer caso, el desarrollador necesita probar que la función run lanza una excepción. **Las excepciones DEBEN comprobarse por su mensaje de error**, ya que cualquier otra excepción también podría lanzarse, invalidando el propósito de la prueba. Si la excepción es una excepción personalizada, el desarrollador PUEDE comprobar la instancia de la excepción.

Para el segundo caso, el desarrollador necesita probar que la función devolvió el mismo número de kilómetros que se le proporcionó.

Para el tercer caso, el desarrollador necesita probar que la función devolvió lo que una función externa, **beLazy** devolvió y, como **doSomething** (también ejecutada en el método) no puede comprobarse mediante el valor de retorno de la función, el desarrollador DEBERÍA **probar que fue llamada con los argumentos correctos**.

```tsx
import { doSomething, beLazy } from '../runningUtils'
jest.mock('../runningUtils')

// Note that there's no main describe
// The only global context are functions that we want to mock with jest

const mockDoSomething = doSomething as jest.MockedFunction<typeof doSomething>
const mockBeLazy = beLazy as jest.MockedFunction<typeof beLazy>

describe('when running more than 1000 kilometers', () => {
	it('should throw an error signaling that the runner can\'t run more than 1000 kilometers', () => {
		expect(() => run(1001)).toThrowError("The runner can't run more than 1000 kilometers")
	})
})

describe('when running less or equal than 10', () => {
	it('should return the same amount of kilometers as the ones given', () => {
		expect(run(2)).toEqual(2)
	})
})

describe('when running more than 10 kilometers but less than 1000', () => {
	const kilometers = 50
	let result: number
	beforeEach(() => {
		mockDoSomething.mockReturnValueOnce(undefined)
		mockBeLazy.mockImplementationOnce(value => value)
		result = run(kilometers)
	})
	
	it('should return the kilometers after being lazy', () => {
		expect(result).toEqual(kilometers)
	})

	it('should call the doSomething function with the given kilometers', () => {
		expect(mockDoSomething).toHaveBeenCalledWith(kilometers)
	})
})
```

## Cuándo hacer mock

El uso de mocks dependerá principalmente del tipo de pruebas que esté escribiendo el desarrollador.

* Las pruebas unitarias DEBERÍAN tener todas las funciones externas mockeadas, con la excepción de funciones que realizan tareas simples, como formatear cadenas, etc.
* Las pruebas de API SOLAMENTE DEBERÍAN tener mocks para la comunicación con servicios externos, es decir, bases de datos o APIs diferentes. Si una de las operaciones realizadas en la prueba afecta el rendimiento del conjunto de pruebas, se podría implementar un mock para mitigar este problema.

Todos los mocks DEBEN realizarse usando las herramientas proporcionadas por Jest, con la excepción de los `redux-saga-test-plan` mocks.

### Mocks y funciones utilitarias

* Cualquier mock de Jest DEBERÍA mockearse usando su **once** método cuando sea posible, es decir, `mockReturnValueOnce` o `mockResolvedValueOnce` se recomienda en contra `mockReturnValue` o `mockResolvedValue`. La razón detrás de esto es prevenir que mocks no deseados se ejecuten, cambiando la ejecución de nuestras pruebas.
* Jest ofrece un conjunto de Types que puedes usar para diferentes tipos de mocks. Por ejemplo, al mockear un objeto DEBERÍAS usar `jest.Mocked<typeof someObject>`, para clases DEBERÍAS usar `jest.MockedClass<typeof SomeClass>`, y para funciones, `jest.MockedFunction<typeof someFunction>`.
* Un `afterEach` DEBERÍA escribirse para ejecutarse después de cada prueba con un `jest.resetAllMocks` para limpiar cualquier implementación mockeada de módulos mockeados globalmente que pueda filtrarse a otras pruebas. Puedes omitir este reset si estás usando `mockReturnValueOnce` o `mockResolvedValueOnce` porque se hace automáticamente por ti.
* Un directorio llamado `mocks` PUEDE crearse para almacenar mocks, que deberían colocarse en el directorio `directorio de prueba` o `spec` . Los mocks grandes DEBEN almacenarse en archivos diferentes a las pruebas para evitar tener archivos de pruebas extensos. Los mocks pequeños, si no son muchos, DEBERÍAN mantenerse en las pruebas.
  * Los archivos mock DEBERÍAN nombrarse como la entidad que están mockeando, así que por ejemplo, si estamos mockeando un profile, el mock debería colocarse bajo el `/test/mocks/profile.ts` archivo.
  * En caso de que se requieran varios mocks grandes, DEBERÍAN colocarse en archivos diferentes dentro de un directorio llamado como la entidad a mockear, así que por ejemplo, si tenemos dos mocks de profile, los almacenaremos como: `/test/mocks/profile/profile-with-wearables.ts` y `/test/mocks/profile/profile-without-wearables.ts`. Para acceder a ellos fácilmente, DEBERÍAS crear un `index.ts` archivo dentro del `/test/mocks/profile/` directorio y exportarlos.
  * Para evitar la mutación de objetos mockeados, los mocks **DEBEN exportarse como funciones** que los devuelvan.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-es/guias-para-colaboradores/testing-standards/writing-tests.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
