# Testing Custom Hooks

Vamos a ver los pasos que tenemos que dar para poder testear los custom hooks que podemos ir desarrollando en nuestras aplicaciones de React y para ello vamos a partir del siguiente custom hook:

```typescript
import { useState } from 'react'

type UseCounterArgs = {
  initialCount?: number
}

export const useCounter = ({ initialCount = 0 }: UseCounterArgs = {}) => {
  const [count, setCount] = useState(initialCount)

  const increment = () => setCount(count + 1)
  const decrement = () => setCount(count - 1)

  return {
    count,
    increment,
    decrement
  }
}
```

Como podemos ver el nuestro custom hook es realmente simple puesto que lo único que hacemos dentro del mismo es definir un atributo del estado `count` al que se le asignará un valor por defecto (que podrá haber sido pasado como parámetro) y se implmentarán las dos funciones que nos van a permitir incrementar o decrementar su valor.

## Test

Vamos a crear los test que nos permitirán probar el correcto funcionamiento de `useCounter`. Para ello lo primero que tenemos que hacer es importar la función `render` de react testing library:

```typescript
import { render } from '@testing-library/react'
```

Ahora importaremos nuestro custom hook pensando que no es más que la función que vamos a querer probar:

```typescript
import { render } from '@testing-library/react'
import { useCounter } from './useCounter'
```

Ahora podemos pasar a utilizar la función `describe` para definir la suite de test que estará asociada a todos los test que tienen que ver con nuestro custom hook:

```typescript
describe('useCounter', () => {})
```

Con esto ya tenemos definida nuestra suite ya podemos pasar a escribir cada uno de los test.

### Testear el valor inicial

El primero de los test que vamos a implementar tendrá que ver con probar que el valor inicial por defecto (es decir, que no se le pasa ningún parámetro a nuestro custom hook) es cero. Así escribiremos:

```typescript
it('should render the initial count', () => {})
```

¿Qué pasos vamos a tener que dar dentro de nuestro test? Pues en primer lugar tendremos que llamar a la función `render` pero en este caso le pasaremos como parámetro el hook que estamos probando:

```typescript
it('should render the initial count', () => {
  render(useCounter)
})
```

Si embargo, si escribimos este código dentro de nuestro editor podemos ver cómo TypeScript nos estará informando de un error en un tooltip:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712567163968/c7c03621-9ae0-457b-94c5-87ecd2c55101.png align="center")

¿Por qué se está produciendo este error? Pues esto es debido a que la función `render` lo que acaba retornando como resultado de su ejecución es código JSX (es decir, un `ReactElement`) y un custom hook no retorna este tipo de datos.

Por lo tanto parece razonable pensar que un custom hook no va a poder ser llamado fuera de un componente de React pero esto vendría a complicar la lógica de nuestros test puesto que al final lo que tendríamos que hacer sería crear un componente de React que sería únicamente utilizado para los test y por lo tanto introducido de una forma artificial en nuestro código.

¿Y por qué no invocamos directamente a la función `useCounter` dentro del código? Es decir, ¿por qué no usamos algo como lo siguiente?

```typescript
it('should render the initial count', () => {
  useCounter()
})
```

El código, desde el punto de vista de TypeScript, es correcto pero el problema lo tenemos en el momento en el que ejecutaremos nuestros test puesto que nos aparecerá un error parecido al que se puede ver en la siguiente imagen:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712567201745/1bce9111-6d7b-4a23-8761-d8f233d4a1b9.png align="center")

Este error básicamente nos viene a decir que estamos haciendo una llamada a un custom hook de forma directa y esto no es posible puesto que los hooks solamente pueden ser invocados dentro de un componente de React.

Entonces ¿cómo vamos a poder probar nuestros hooks? La respuesta está una vez más en la react testing library puesto que nos proporciona la función `renderHook` para este propósito por lo que esta será la función que tendremos que importar en vez de `render`:

```typescript
import { renderHook } from '@testing-library/react'
```

Ahora tenemos que pasar a usar esta función en nuestro hook sabiendo que como parámetro lo que espera recibir es el custom hook que vamos a probar. Así pues escribiremos:

```typescript
it('should render the initial count', () => {
  renderHook(useCounter)
})
```

Pero ¿cómo podemos hacer ahora nuestras aserciones? En el caso de un estar testeando un componente de React ya hemos visto que es gracias al uso del objeto `screen` que nos proporciona react testing library en el caso de los custom hook no se va a poder hacer uso de este objeto. Aquí es donde tenemos que saber que react testing library internamente lo que está haciendo cuando invocamos a `renderHook` es envolver el hook que recibe como parámetro en un funcional componente y retornará el resultado de la invocación en un objeto donde en su atributo `result` tendremos el resultado de la invocación del custom hook.

```typescript
it('should render the initial count', () => {
  const { result } = renderHook(useCounter)
})
```

Tenemos además que saber que dentro del objeto `result` tenemos a nuestra disposición el atributo `current` que lo que viene a tener son todos los valores que han sido retornados por el custom hook que estamos probando.

Con esto en mente ya podemos crear nuestra aserción puesto que uno de los atributos de `current` en nuestro ejemplo, será `count` y por lo tanto podemos escribir algo como:

```typescript
it('should render the initial count', () => {
  const { result } = renderHook(useCounter)
  expect(result.current.count).toBe(0)
})
```

Si ahora guardamos nuestro trabajo y ejecutamos el test podemos ver que este pasará sin problemas tal y como esperábamos:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712567300717/b7241918-c5c2-4069-abff-5874c5772a7b.png align="center")

De hecho si cambiamos nuestra aserción intentado asegurar que el valor que se recibe es diferene de cero:

```typescript
it('should render the initial count', () => {
  const { result } = renderHook(useCounter)
  expect(result.current.count).toBe(1)
})
```

podemos ver como en la consola nos aparecerá un error tal y como esperábamos:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712567324380/eea9fa39-9152-4b3d-878b-f550883555ed.png align="center")

### Test valor inicial cuando se pasa como parámetro

Vamos con el segundo de nuestros test en el cual vamos a probar que nuestro custom hook aceptará el valor inicial que ha de tener el contador. Así pues definimos:

```typescript
it('should accept and render the same initial count', () => {})
```

Antes hemos visto que para poder llamar a nuestro custom hook lo que vamos a usar es la función `renderHook` la cual espera recibir como parámetro el custom hook que vamos a probar `useCounter` pero ¿cómo podemos pasarle a este custom hook los parámetros con el que queremos invocarlo? La respuesta es sabiendo que `renderHook` permite tener un segundo parámetro que será un objeto con las opciones que queremos que se apliquen.

De entre todos los atributos que pueden venir definidos en este objeto de opciones tenemos a nuestra disposición `initialProps` a la que le vamos a poder asignar un objeto donde definiremos un atributo por cada uno de los parámetros que espera recibir nuestro custom hook y el valor que tiene asignado. Así, si quieremos que en nuestro test se pruebe que el valor inicial es 10 escribiríamos algo como:

```typescript
it('should accept and render the same initial count', () => {
  renderHook(useCount, {
    initialProps: {
      initialCount: 10
    }
  })
})
```

> Hemos definido un objeto como el valor de `initialProps` como un objeto puesto que es lo que espera recibir.

Ahora el proceso de definición de nuestro test será igual que en el caso anterior puesto que lo que haremos será quedarnos con el atributo `result` como sigue donde además escribiremos la aserción para comprobar que el valor que tendrá el contador será 10:

```typescript
it('should accept and render the same initial count', () => {
  const { result } = renderHook(useCount, {
    initialProps: {
      initialCount: 10
    }
  })

  expect(result.current.count).toBe(10)
})
```

Si ahora guardamos nuestro los cambios y volvemos a ejecutar los test podrems ver que los dos pasarán correctamente tal y como esperábamos:

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1712567410231/ad12204c-cd27-4a85-88cd-0793ddb42f4d.png align="center")

El código completo del test que hemos escrito lo podemos encontrar a continuación:

```typescript
import { renderHook } from '@testing-library/react'
import { useCounter } from './useCounter'

describe('useCounter', () => {
  it('should render the initial count', () => {
    const { result } = renderHook(useCounter)
    expect(result.current.count).toBe(0)
  })

  it('should accept and render the same initial count', () => {
    const { result } = renderHook(useCount, {
      initialProps: {
        initialCount: 10
      }
    })

    expect(result.current.count).toBe(10)
  })
})
```

## Resumen

* Cuando vamos a testear nuestros custom hooks tenemos que apoyarnos en el uso de la función `renderHook` que nos proporciona react testing library. Esta función espera recibir como parámetro el custom hook que vamos a probar.
    
* La invocación de `renderHook` retornará un objeto que posee el atributo `result` que tiene a su vez el atributo `current` cuyo valor será un objeto que contiene como atributos todos los valores que retorna el custom hook que ha recibido como parámetro en su invocación.
    
* En el caso de tener que pasar parámetros al custom hook lo podremos hacer gracias al segundo parámetro que puede recibir `renderHook` que no es más que un objeto con las opciones que queremos que aplique. En este caso haríamos uso del atributo `initialProps`.
    

> **Nota:** dejamos para un próximo artículo el ver cómo podemos probar las funciones `increment` y `decrement` de nuestro custom hook de ejemplo.
