Know this easily test React app
Tags: testing, react, javascript, webdev
Published: 2021-10-31 02:02:21

---
canonical_url: "https://www.thangphan.xyz/posts/know-this-easily-test-react-app/"
---
_Jest_ and _Testing Library_ were the most powerful tool for testing React App. In this post, we are going to discover the important concept of them.

Let's dig in!

This is the simplest test that we can write in the first time using _Jest_.

```javascript
test('1 plus 2 equal 3', () => {
  expect(1 + 2).toBe(3)
})
```

### Test Asynchronous

Suppose that I have a fake API that returns the user response with `id: 1`, in the test case, I intentionally set change `id: 3` to check whether the test works properly or not, and I end up with a `passed` message.

The reason is that the test case is completed before the promise finishes.

```tsx
test('user is equal user in response', () => {
  const user = {
    userId: 1,
    id: 3,
    title: 'delectus aut autem',
    completed: false,
  }

  fetch('https://jsonplaceholder.typicode.com/todos/1')
    .then((response) => response.json())
    .then((json) => expect(user).toEqual(json))
})
```

In order to avoid this bug, we need to have `return` in front of `fetch`.

```tsx
test('user is equal user in response', () => {
  const user = {
    userId: 1,
    id: 3,
    title: 'delectus aut autem',
    completed: false,
  }

  return fetch('https://jsonplaceholder.typicode.com/todos/1')
    .then((response) => response.json())
    .then((json) => expect(user).toEqual(json))
})
```

The test case above can rewrite using `async, await`:

```tsx
test('user is equal user in response using async, await', async () => {
  const user = {
    userId: 1,
    id: 2,
    title: 'delectus aut autem',
    completed: false,
  }

  const res = await fetch('https://jsonplaceholder.typicode.com/todos/1')
  const resJson = await res.json()

  expect(user).toEqual(resJson)
})
```

### Useful methods

`beforeAll`: To add some code that we want to run once before the test cases is run.

`afterAll`: To add some code that we want to run after all test cases are finished. e.g. clear the database.

`beforeEach`: To add some code that we want to run before each test case.

`afterEach`: To add some code that we want to run at the point that each test case finishes.

Suppose that I have three test cases, and I set:

```tsx
beforeEach(() => {
  console.log('beforeEach is working...')
})
```

Three `console` will appear on my terminal. Conversely, Using `beforeAll` I only see one `console`.

The logic way is the same with `afterEach` and `afterAll`.

### The order run

We already have `describe`(combines many test cases), `test`(test case).

What is the order that jest run if test file was mixed by many `describe`, `test`?

You only need to remember this order: `describe` -> `test`.

To illustrate:

```tsx
describe('describe for demo', () => {
  console.log('this is describe')

  test('1 plus 2 equal 3', () => {
    console.log('this is test case in describe')

    expect(1 + 2).toBe(3)
  })

  describe('sub-describe for demo', () => {
    console.log('this is sub-describe')

    test('2 plus 2 equal 4', () => {
      console.log('this is test case in sub-describe')

      expect(2 + 2).toBe(4)
    })
  })
})
```

_Can you spot on the order in the example above?_

My terminal log:

- this is describe

- this is sub-describe

- this is test case in describe

- this is test case in sub-describe

### Mock function

I think the most powerful of Jest is having a mock function that we are able to mock the `params`, `object` which defined by the `new` keyword, and customize the return value.

This is an example:

```tsx
function plusTwoNumbers(
  list: Array<number>,
  callback: (a: number, b: number) => void,
) {
  callback(list[0], list[1])
}

test('mock function callback', () => {
  const mockFnc = jest.fn((a, b) => console.log('total:', a + b))

  plusTwoNumbers([1, 2], mockFnc)
})
```

We mock `callback` function, get the `params` of it, and customize the result `console.log("total:", a + b)`.

We are also able to mock modules, e.g. I use `uuid` in order to generate a unique `id`.

When I move on to testing, instead of using `uuid`, I can mock the `uuid` module like the code below:

Normally, whenever I call `uuid.v4()` I will get a random value like this: `5442486-0878-440c-9db1-a7006c25a39f`

But I want my value to be `1234`, I can use the code below:

```tsx
import * as uuid from 'uuid'

jest.mock('uuid')

test('mock uuid module', () => {
  uuid.v4.mockReturnValue('1234')

  console.log('uuid.v4()', uuid.v4())

  // 1234
})
```

Otherwise, I can use `mockImplementation` to customize.

```tsx
uuid.v4.mockImplementation(() => '1234')
```

_`mockImplementation` is the function that we customize the function that is created from other modules._

### Config Jest

I'm going to introduce to you about the most important configs in Jest.

Let's go!

- `collectCoverageFrom`

This config helps Jest knows exactly the place that needs to collect information, and check coverage. It is very useful, you can run:

Run `jest --coverage` in order to figure out the component, the function, we still need to write test, and discover the spots we still don't test yet.

- `moduleDirectories`

This config points to the `module` that we will use in the `test` file.

By default, it was configured `["node_modules"]`, and we are able to use the the module under `node_modules` folder in our test cases.

- `moduleNameMapper`

This config provides for us the ability to access the resources, based on the place that we have set.

```tsx

moduleNameMapper: {
  "assets/(*)": [
    "<rootDir>/images/$1"
  ]
}
```

See the example above, now we set the path `assets/(*)` that pointed to `<rootDir>/images/$1`.

If I set `assets/logo.png`, Jest will find `<rootDir>/images/logo.png`.

- `rootDir`

By default, it is the place that contains `jest.config.js`, `package.json`.

The place is where Jest will find to use `modules`, and run test cases.

It turns out I can set "rootDir: '**test**'" and run test cases without config `roots`, but I shouldn't do this.

- `roots`

This is the config that we set the place that test files belong to.

For example:

If I set:

```tsx
roots: ['pages/']
```

but I write test in `__test__` folder which is the same level with `pages/`. No test cases will be run with the config above. I need to change `pages/` -> `__test__`.

- `testMatch`

We use this config in order to communicate to Jest what files we want to test, otherwise, please skip!

- `testPathIgnorePatterns`

Please ignore files under a place, that is the reason this config exists.

- `transform`

Sometimes, in our test cases, we write some new code that `node` doesn't support at all, so we need to transform to the code that Jest can understand.

If my project use `typescript`, I need to set up transform in order to make `typescript` to `javascript` code that node can understand.

- `transformIgnorePatterns`

We might have some files, some folders we don't want to transform, so we use this config.

### How to write test

We need to write tests in order to be more confident about the code that we wrote. So when we think about the test cases, the core concept is we have to think about the use case, do not think about the code. It means we must focus
into what's the future that the code can support for users.

This is the main concept when we think about creating `test cases`.

e.g:

I have created a react-hook in order to support four features below:

1. returns the value in first data using first property, condition true.

2. returns the value in second data using second property, condition false.

3. returns the value in second data using first property, condition false.

4. returns the default value with second data undefined, condition false.

```tsx
import * as React from 'react'

type Props<F, S> = {
  condition: boolean
  data: [F, S]
}

function useInitialState<F, S>({condition, data}: Props<F, S>) {
  const giveMeState = React.useCallback(
    (
      property: keyof F,
      anotherProperty: S extends undefined ? undefined : keyof S | undefined,
      defaultValue: Array<string> | string | number | undefined,
    ) => {
      return condition
        ? data[0][property]
        : data[1]?.[anotherProperty ?? (property as unknown as keyof S)] ??
            defaultValue
    },

    [condition, data],
  )

  return {giveMeState}
}

export {useInitialState}
```

So I only need to write four test cases for the four features above:

```tsx
import {useInitialState} from '@/utils/hooks/initial-state'

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

describe('useInitialState', () => {
  const mockFirstData = {
    name: 'Thang',
    age: '18',
  }

  test('returns the value in first data using first property, condition true', () => {
    const mockSecondData = {
      name: 'Phan',
      age: 20,
    }

    const {result} = renderHook(() =>
      useInitialState({
        condition: Boolean(mockFirstData),
        data: [mockFirstData, mockSecondData],
      }),
    )

    const data = result.current.giveMeState('name', undefined, '')

    expect(data).toBe(mockFirstData.name)
  })

  test('returns the value in second data using second property, condition false', () => {
    const mockSecondData = {
      firstName: 'Phan',
      age: 20,
    }

    const {result} = renderHook(() =>
      useInitialState({
        condition: Boolean(false),
        data: [mockFirstData, mockSecondData],
      }),
    )

    const data = result.current.giveMeState('name', 'firstName', '')

    expect(data).toBe(mockSecondData.firstName)
  })

  test('returns the value in second data using first property, condition false', () => {
    const mockSecondData = {
      name: 'Phan',
      age: 20,
    }

    const {result} = renderHook(() =>
      useInitialState({
        condition: Boolean(false),
        data: [mockFirstData, mockSecondData],
      }),
    )

    const data = result.current.giveMeState('name', undefined, '')

    expect(data).toBe(mockSecondData.name)
  })

  test('returns the default value with second data undefined, condition false', () => {
    const mockDefaultValue = 21

    const {result} = renderHook(() =>
      useInitialState({
        condition: Boolean(false),
        data: [mockFirstData, undefined],
      }),
    )

    const data = result.current.giveMeState('age', undefined, mockDefaultValue)

    expect(data).toBe(mockDefaultValue)
  })
})
```

### Testing Library

Let's take a slight review about the main things in _Testing Library_.

- **getBy..**: we find the DOM element, throw error if no element is found.
- **queryBy..**: we find the DOM element, return null if no element is found.
- **findBy..**: we find the DOM element, throw an error if no element is found,
  the search process is a promise.

The list below is the priority we should use in order to write test nearer with the way that our app is used.

- _getByRole_

- _getByLabelText_

- _getByAltText_

- _getByDisplayValue_

For example:

I have a component that contains two components: `AutoAddress`, `Address`.I need to find the use case that I want to support in order to create test cases.

This is a test case: `by default, name value of inputs was set`.

1. render the components

2. create the mockResult value

3. add assertions

```tsx
test('by default, name of address input was set', async () => {
  render(
    <AutoAddress wasSubmitted={false}>
      <Address wasSubmitted={false} />
    </AutoAddress>,
  )

  const mockResult = {
    namePrefectureSv: 'prefertureSv',
    namePrefectureSvLabel: 'prefectureSvLabel',
    nameCity: 'city',
  }

  expect(screen.getByLabelText('Prefecture Code')).toHaveAttribute(
    'name',
    mockResult.namePrefectureSv,
  )

  expect(screen.getByLabelText('Prefecture')).toHaveAttribute(
    'name',
    mockResult.namePrefectureSvLabel,
  )

  expect(screen.getByLabelText('City')).toHaveAttribute(
    'name',
    mockResult.nameCity,
  )
})
```

And this is a test case: `returns one address through postCode`.

1. render the components

2. create the mockResult value

3. mock the request API

4. input the postCode

5. click the search button

6. add assertions

```tsx
test('returns one address through postCode', async () => {
  const mockResult = [
    {
      id: '14109',
      zipCode: '1880011',
      prefectureCode: '13',
      city: 'Tokyo',
    },
  ]

  server.use(
    rest.get(
      `${process.env.NEXT_PUBLIC_API_OFF_KINTO}/${API_ADDRESS}`,
      (req, res, ctx) => {
        return res(ctx.json(mockResult))
      },
    ),
  )

  render(
    <AutoAddress wasSubmitted={false}>
      <Address wasSubmitted={false} />
    </AutoAddress>,
  )

  // input the post code value

  userEvent.type(screen.getByLabelText('first postCode'), '111')
  userEvent.type(screen.getByLabelText('second postCode'), '1111')

  // search the address

  userEvent.click(screen.getByRole('button', {name: /search address/i}))

  // wait for the search process finishes.

  await waitForElementToBeRemoved(() =>
    screen.getByRole('button', {name: /searching/i}),
  )

  const address = mockResult[0]
  const {prefectureCode, city} = address

  expect(screen.getByLabelText('Prefecture Code')).toHaveAttribute(
    'value',
    prefectureCode,
  )

  expect(screen.getByLabelText('Prefecture')).toHaveAttribute(
    'value',
    PREFECTURE_CODE[prefectureCode as keyof typeof PREFECTURE_CODE],
  )

  expect(screen.getByLabelText('City')).toHaveAttribute('value', city)
})
```

### Recap

We just learned the main concepts in Testing React App! Let's recap some key points.

- Testing asynchronous need to have `return` in front of `promise`.
- We are able to control testing using _Jest_ configs.
- Thinking test cases, we must forget about code, focus on the use case.
- The order of DOM methods in _Testing Library_.