> For the complete documentation index, see [llms.txt](https://docs.decentraland.org/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.decentraland.org/contributor/contributor-pt/guias-do-contribuidor/testing-standards/testing-uis.md).

# Testar UIs

Esta seção descreve como os desenvolvedores testarão a base de código relacionada à UI.

## Stack de testes

Todos os nossos testes de UI DEVEM ser feitos usando [Jest](https://jestjs.io/) como o framework principal de testes com [redux-saga-test-plan](https://github.com/jfairbank/redux-saga-test-plan) como a ferramenta de teste de sagas e [react testing library](https://testing-library.com/docs/react-testing-library/intro/) como ferramenta de teste de componentes UI. O código dos testes DEVE ser escrito usando [Typescript](https://www.typescriptlang.org/) e executado usando [ts-jest](https://github.com/kulshekhar/ts-jest) para ter suporte à verificação de tipos.

## O que testar

* Componentes UI
* Sagas
* Reducers
* Selectors
* Action creators
* Funções utilitárias

## Testando sagas

Sagas nos permitem lidar com efeitos colaterais permitindo criar geradores que tratam ações do redux. Uma das principais vantagens das sagas é sua testabilidade, já que os efeitos amplamente usados em sagas são altamente testáveis.

Esses handlers DEVEM ser testados usando [redux-saga-test-plan](https://github.com/jfairbank/redux-saga-test-plan), e o teste DEVE exercitar toda a execução, desde o handler do módulo inteiro (seu handler principal ou sagas) por toda a execução dos handlers que irão reagir à ação que vamos despachar. Essa abordagem torna os testes de sagas fáceis de construir e manter, e traz valor aos testes ao percorrerem o fluxo o máximo possível.

Os testes dos handlers DEVEM ser testados através de sua interação com os efeitos, principalmente com o `put` um, já que seu propósito é lidar com ações e produzir novas. A maioria dos testes certamente conterá um `provide` call, usado para mockar efeitos, uma ou múltiplas `put` calls, para verificar que os handlers disparam as ações conforme esperado e um único `dispatch` seguido por um `run` call para executar toda a saga até a conclusão.

O mock de recursos externos ao módulo que está sendo testado DEVE ser feito usando os providers do redux-saga-test-plan aproveitando os diferentes efeitos fornecidos pelo redux-saga. Efeitos como call, apply ou outros DEVEM ser usados sempre que possível quando uma função é chamada em um teste para permitir mocká‑los facilmente. Jest NÃO DEVE ser usado para mockar módulos ou funções.

O texto no describe e seus DEVE seguir estas diretrizes:

* O describe principal de uma saga ou handler principal DEVE começar com *when handling*, indicando que o que será testado é um handler. Seguindo a *when handling* frase, uma descrição da ação (ou a intenção que a ação) a ser tratada DEVE ser escrita. Ex.: *when handling the action that signals a successful fetch.*
* Describes internos diferenciarão contextos de execução, eles seguem as mesmas regras descritas na [Describing and building context](/contributor/contributor-pt/guias-do-contribuidor/testing-standards/writing-tests.md#describing-and-building-context) seção.
* O **it**s DEVEM descrever o que é esperado do teste, começando com um *should put* e a descrição das ações que devem ser put ao executar o handler.

### Exemplo de sagas

```tsx
...
export function* tiersSaga(builder: BuilderAPI) {
	function* handleBuyThirdPartyItemTierRequest(action: BuyThirdPartyItemTiersRequestAction) {
    const { thirdParty, tier } = action.payload
    try {
			// Getting the chain id can fail, a test must be made to ensure that this scenarios is excercised
      const maticChainId: ChainId = yield call(getChainIdByNetwork, Network.MATIC)
      const thirdPartyContract = yield call(getContract, ContractName.ThirdPartyRegistry, maticChainId)
			// Sending the transasction can either success or fail, both cases must be exercised in a test
      const txHash: string = yield call(sendTransaction, thirdPartyContract, instantiatedThirdPartyContractContract =>
        instantiatedThirdPartyContractContract.buyItemSlots(thirdParty.id, tier.id, tier.price)
      )
      yield put(buyThirdPartyItemTiersSuccess(txHash, maticChainId, thirdParty, tier))
    } catch (error) {
      yield put(buyThirdPartyItemTiersFailure(error.message, thirdParty.id, tier))
    }
  }
}
```

### Exemplo de teste de sagas

```tsx
// The main describe starts with 'when handling...'
describe('when handling the request to buy a third party item tier', () => {
	// All cases, negative and positive are exercised
  describe('and the chain id couldn\'t be retrieved', () => {
    it('should put the action signaling the failure of the purchase of an item slots tier with the tier and the third party id', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
				// Provide calls mock the different effects, building different scenarios
        .provide([[call(getChainIdByNetwork, Network.MATIC), Promise.reject(new Error(defaultError))]])
				// Put calls verify that what was expected as the end result happened
        .put(buyThirdPartyItemTiersFailure(defaultError, thirdParty.id, thirdPartyItemTier))
				// A single dispatch per test is done to exercise the handling of the action that we're testing
        .dispatch(buyThirdPartyItemTiersRequest(thirdParty, thirdPartyItemTier))
        .run({ silenceTimeout: true })
    })
  })

  describe('and sending the transaction fails', () => {
    it('should put the action signaling the failure of the purchase of an item slots tier with the tier and the third party id', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
        .provide([
					// Call match the exact call effect, taking into consideration the parameters
					// This makes our test ensure that the code executed before a put did what we expected
          [call(getChainIdByNetwork, Network.MATIC), ChainId.MATIC_MUMBAI],
          [call(getContract, ContractName.ThirdPartyRegistry, ChainId.MATIC_MUMBAI), contract],
					// Matchers are used to only match the function and not the parameters
          [matchers.call.fn(sendTransaction), Promise.reject(new Error(defaultError))]
        ])
        .put(buyThirdPartyItemTiersFailure(defaultError, thirdParty.id, thirdPartyItemTier))
        .dispatch(buyThirdPartyItemTiersRequest(thirdParty, thirdPartyItemTier))
        .run({ silenceTimeout: true })
    })
  })

  describe('and sending the transaction succeeds', () => {
    let contract: any
    beforeEach(() => {
      contract = { buyItemSlots: jest.fn() }
    })

    it('should put the action signaling the successful purchase of an item slots tier with the tier, the third party and the transaction details', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
        .provide([
          [call(getChainIdByNetwork, Network.MATIC), ChainId.MATIC_MUMBAI],
          [call(getContract, ContractName.ThirdPartyRegistry, ChainId.MATIC_MUMBAI), contract],
          [matchers.call.fn(sendTransaction), Promise.resolve(txHash)]
        ])
        .put(buyThirdPartyItemTiersSuccess(txHash, ChainId.MATIC_MUMBAI, thirdParty, thirdPartyItemTier))
        .dispatch(buyThirdPartyItemTiersRequest(thirdParty, thirdPartyItemTier))
        .run({ silenceTimeout: true })
    })
  })
})
```

## Testando reducers

Reducers são funções que recebem um state e uma action e retornam um novo state. Esse novo state PODE conter alterações programadas para ocorrer para a ação dada.

Os testes DEVEM variar os estados iniciais e os diferentes parâmetros da action a ser testada se necessário. As asserções DEVEM ser feitas sobre todo o estado retornado, já que uma action pode modificar qualquer parte do state inicial fornecido.

Ao reduzir uma action, DEVEMOS usar o action creator, tornando os testes mais fáceis de manter.

Para padronizar a forma como escrevemos **describe**s e **it**s:

* Describes principais DEVEM começar descrevendo quais ações serão reduzidas usando a frase *when reducing the action* seguida por uma descrição da ação. NÃO DEVEMOS usar o tipo da action para descrever a ação a ser testada, pois o tipo pode mudar.
* O **it**s DEVEM descrever de forma clara como o state retornado mudou. Ex.: *it should return a state with the error nulled and the fruits set*.

### Exemplo de reducer

```tsx
const INITIAL_STATE: FruitsState = {
  data: {
    fruits: []
  },
  loading: []
}

export function fruitsReducer(state = INITIAL_STATE, action: any): FruitsState {
  switch (action.type) {
    case FETCH_FRUIT_REQUEST: {
      return {
        ...state,
        loading: loadingReducer(state.loading, action),
				error: null
      }
    }
		case FETCH_FUIT_ERROR: {
			const { error } = action
      return {
        ...state,
				error,
        loading: loadingReducer(state.loading, action)
      }
		}
		case FETCH_FRUIT_SUCCESS: {
			const { fruits } = action
      return {
        ...state,
				fruits,
        loading: loadingReducer(state.loading, action)
      }
		}
}
```

### Exemplo de teste de reducers

```tsx
let state: FruitsState

beforeEach(() => {
	state = {
	  data: {
	    fruits: []
	  },
	  loading: [],
		error: null
	}
})

describe('when reducing the action that signals the fetching of fruits', () => {
	beforeEach(() => {
		// An error is set to ensure that it gets cleared when reducing the action.
		state.error = 'anError'
	})

	it('should return a state where the error is cleared and the loading state is set to loading', () => {
		// Action creators are used to test the reducer so it can be maintained with ease.
		const action = fetchFruitsRequest()

		expect(fruitsReducer(state, action)).toEqual({
			...state,
			error: null,
			/* As the loading reducer is part of another reducer (and doesn't need to be tested),
				 we MAY choose to use it as is or we MAY use a helper that emulates it.
			*/
			loading: loadingReducer(state.loading, action),
		})
	})
})

describe('when reducing the action that signals the failure of fetching fruits', () => {
	const error = 'anError'

	it('should return a state where the error is set and the loading state is cleared from the fruits fetch action', () => {
		const action = fetchFruitsFailure(error)

		expect(fruitsReducer(state, action)).toEqual({
			...state,
			error,
			loading: loadingReducer(state.loading, action),
		})
	})
})

describe('when reducing the action that signals the success of fetching fruits', () => {
	let fruits: string[]
	beforeEach(() => {
		fruits = ['apples', 'pears']
	})

	it('should return a state where the fruits are set and the loading reducer is cleared from the fruits fetch action', () => {
		const action = fetchFruitsSuccess(fruits)

		expect(fruitsReducer(state, action)).toEqual({
			...state,
			fruits,
			loading: loadingReducer(state.loading, action),
		})
	})
})
```

## Testando selectors

Selectors são métodos que recebem um state e retornam uma seção desse state ou uma seção transformada do state.

Existem dois tipos de selectors, selectors memoizados e selectors normais ou comuns. Selectors normais ou comuns DEVEM ser testados como qualquer outra função unitária testada e selectors memoizados DEVERIAM ser testados usando o `resultFunc` ou a função que recebe as partes memoizadas do state e retorna o valor desejado.

**Describe**s e **it**s DEVEM ser escritos conforme descrito em [Describing and building context](/contributor/contributor-pt/guias-do-contribuidor/testing-standards/writing-tests.md#describing-and-building-context) seção.

### Exemplo de Selectors

```tsx
// Common selector
export const collectionExists = (state: RootState, id: string): boolean => {
	return state.collections.data.some((collection) => collection.id === id)
}

// Memoized selector using reselect that returns a list of enhanced collections
export const getEnhancedCollections = createSelector(
	getCollections,
	getExtraData,
	(collections, extraData) => {
		return collections.map((collection) =>
			({ ...collection, ...extraData[collection[id]] }))
	}
)
```

### Exemplo de teste de selectors

```tsx
let state: RootState
let collection: { id: string }
beforeEach(() => {
	collection = {
		id: 'anId'
	}
	state = {
		collections: {
			data: []
		}
	}
})

beforeEach(() => {
	state.collections.data = [collection]
})

// Common selectors are unit tested as normal functions
describe('when checking if a collection exists', () => {
	describe('and there\'s no collection with the given id', () => {
		it('should return false', () => {
			expect(collectionExists(state, "anotherId")).toBe(false)
		})
	})

	describe('and there\'s a collection with the given id', () => {
		it('should return true', () => {
			expect(collectionExists(state, collection.id)).toBe(true)
		})
	})
})

describe('when getting the enhanced collections', () => {
	let enhacements: Record<string, Enhacement>

	describe('and there\'s no data to enhance them', () => {
		beforeEach(() => {
			enhacements = {}
		})

		// Memoized selectors should be unit tested using resultFunc
		it('should retrieve all the collections without enhancing them', () => {
			expect(getEnhancedCollections.resultFunc(collections, enhacements)).toEqual([
				collection
			])
		})
	})

	describe('and there\'s data to enhance them', () => {
		beforeEach(() => {
			enhacements = { [collection.id]: { aNewProperty: true } }
		})

		it('should retrieve all the enhanced collections', () => {
			expect(getEnhancedCollections.resultFunc(collections, enhacements)).toEqual([
				{ ...collection, ...enhacements[collection.id] }
			])
		})
	})
})
```

## Testando action creators

Action creators são responsáveis por criar as ações que mais tarde serão processadas nos reducers. Embora action creators geralmente sejam simples, ELES DEVEM ser testados para garantir que façam o que esperamos.

**Describe**s e **it**s DEVEM ser escritos conforme descrito em [Describing and building context](/contributor/contributor-pt/guias-do-contribuidor/testing-standards/writing-tests.md#describing-and-building-context) seção.

### Exemplo de action creators

```tsx
export const BUY_THIRD_PARTY_ITEM_TIERS_REQUEST = '[Request] Buy a third party item tier'
export const BUY_THIRD_PARTY_ITEM_TIERS_SUCCESS = '[Success] Buy a third party item tier'
export const BUY_THIRD_PARTY_ITEM_TIERS_FAILURE = '[Failure] Buy a third party item tier'

export const buyThirdPartyItemTiersRequest = (thirdParty: ThirdParty, tier: ThirdPartyItemTier) =>
  action(BUY_THIRD_PARTY_ITEM_TIERS_REQUEST, { thirdParty, tier })
export const buyThirdPartyItemTiersSuccess = (txHash: string, chainId: ChainId, thirdParty: ThirdParty, tier: ThirdPartyItemTier) =>
  action(BUY_THIRD_PARTY_ITEM_TIERS_SUCCESS, { thirdParty, tier, ...buildTransactionPayload(chainId, txHash, { tier, thirdParty }) })
export const buyThirdPartyItemTiersFailure = (error: string, thirdPartyId: string, tier: ThirdPartyItemTier) =>
  action(BUY_THIRD_PARTY_ITEM_TIERS_FAILURE, { error, thirdPartyId, tier })
```

### Exemplo de teste de action creator

```tsx
describe('when creating the action that signals the start of a tiers fetch request', () => {
  it('should return an action signaling the start of the tiers fetch', () => {
    expect(fetchThirdPartyItemTiersRequest()).toEqual({ type: FETCH_THIRD_PARTY_ITEM_TIERS_REQUEST })
  })
})

describe('when creating the action that signals the successful fetch of tiers', () => {
  it('should return an action signaling the success of the tiers fetch', () => {
    expect(fetchThirdPartyItemTiersSuccess([thirdPartyItemTier])).toEqual({
      type: FETCH_THIRD_PARTY_ITEM_TIERS_SUCCESS,
      payload: { tiers: [thirdPartyItemTier], error: undefined, meta: undefined }
    })
  })
})

describe('when creating the action that signals the unsuccessful fetch of tiers', () => {
  it('should return an action signaling the failure of the tiers fetch', () => {
    expect(fetchThirdPartyItemTiersFailure(defaultError)).toEqual({
      type: FETCH_THIRD_PARTY_ITEM_TIERS_FAILURE,
      payload: { error: defaultError }
    })
  })
})
```

## Testando componentes ui

Para testar componentes UI estamos usando [React Testing Library](https://testing-library.com/docs). Estamos usando esta biblioteca em vez de outras opções como enzyme, pois ela nos permite testar componentes levando em conta como o usuário irá interagir com ele em vez da implementação em si. Não iremos interagir com instâncias de componentes React; em vez disso, interagimos com elementos do DOM na forma como são renderizados.

### O que testar

O propósito deste teste de componentes ui é testar o comportamento do componente e como o usuário interage com ele. Em muitos casos pode haver componentes que estão conectados ao redux store para obter algumas propriedades e chamar algumas ações. Geralmente lidamos com isso criando um `container` arquivo separado que irá interagir com a store. Esses arquivos estão fora do escopo deste tipo de teste. Estamos testando como o componente se comporta com diferentes valores de props; não devemos nos preocupar com a origem desses valores (redux store, context, componente pai), mas sim com como eles impactam a renderização final.

### Renderização

Cada teste deve começar chamando a função render. Isso irá renderizar não apenas o componente que estamos passando como parâmetro, mas também todos os filhos. Devemos evitar fazer isso na configuração do teste (`beforeEach`) pois pode causar problemas fazendo com que a árvore do DOM tenha mais componentes do que queremos. Devemos salvar o resultado em uma propriedade chamada **screen**. Fazer isso nos permitirá obter todas as propriedades necessárias no teste sem a necessidade de alterar constantemente a desestruturação.

```jsx
it('should render hello world component', () => {
	const screen = render(<HelloWorld />)
})
```

Se o documento tiver múltiplos testes e o componente tiver múltiplas props, podemos criar uma função no topo do arquivo que cuidará de definir props padrão e renderizar o componente.

```jsx
function renderSelectedFilters(props: Partial<Props> = {}) {
  return render(
    <SelectedFilters
      isLandSection={false}
      category={undefined}
      browseOptions={{}}
      onBrowse={jest.fn()}
      {...props}
    />
  )
}
```

### Queries

Queries são os métodos que a Testing Library fornece para encontrar elementos na página. Existem diferentes [tipos de queries](https://testing-library.com/docs/queries/about/#priority) que podemos usar para acessar os elementos. Devemos tentar sempre usar as queries que são `Accessible to everyone` pois são as que refletem a experiência para todos os usuários (visual/mouse e com tecnologia assistiva).

Essas queries são `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByDisplayValue`. Se pudermos acessar o elemento usando essas queries, provavelmente significa que o componente não é acessível.

### Testando eventos de usuário

Não poderemos acessar funções dentro do componente, então devemos imitar o comportamento do usuário que leva a essas funções serem chamadas. Para isso estamos usando `userEvent` library.

```jsx
const screen = renderSelectedFilters({
      browseOptions: raritiesOptions,
      onBrowse: onBrowseMock
    })
const commonPill = screen.getByTestId('pill-common')
await userEvent.click(within(commonPill).getByRole('button'))
```

### Depuração

Para depurar o teste e qual é o estado atual do DOM em um momento específico podemos usar `debug` função que é uma das propriedades retornadas ao chamar render

```jsx
const { debug } = render(<HelloWorld />)

debug()
```

### Exemplo completo de teste de componente ui

```jsx
function renderSelectedFilters(props: Partial<Props> = {}) {
  return render(
    <SelectedFilters
      isLandSection={false}
      category={undefined}
      browseOptions={{}}
      onBrowse={jest.fn()}
      {...props}
    />
  )
}

describe('rarities filter', () => {
  it('should render rarities', () => {
    const raritiesOptions = { rarities: [Rarity.COMMON, Rarity.EPIC] }
    const screen = renderSelectedFilters({
      browseOptions: raritiesOptions
    })
    expect(screen.getByText(Rarity.COMMON)).toBeInTheDocument()
    expect(screen.getByText(Rarity.EPIC)).toBeInTheDocument()
  })

  it('should call onBrowse without deleted rarity', async () => {
    const raritiesOptions = { rarities: [Rarity.COMMON, Rarity.EPIC] }
    const onBrowseMock = jest.fn()

    const screen = renderSelectedFilters({
      browseOptions: raritiesOptions,
      onBrowse: onBrowseMock
    })
    const commonPill = screen.getByTestId('pill-common')
    await userEvent.click(within(commonPill).getByRole('button'))
    expect(onBrowseMock).toHaveBeenCalledWith({ rarities: [Rarity.EPIC] })
  })
})
```

## Estrutura de diretórios

Os testes DEVEM ser colocados ao lado dos arquivos ou módulos que eles irão exercitar, com o mesmo nome, mas com a extensão `spec.ts` em vez de `ts`.

Uma estrutura de arquivos DEVE ficar assim:

```
actions.ts
action.spec.ts
reducer.ts
reducer.spec.ts
sagas.ts
sagas.spec.ts
selectors.ts
selectors.spec.ts
utils.ts
utils.spec.ts
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

```
GET https://docs.decentraland.org/contributor/contributor-pt/guias-do-contribuidor/testing-standards/testing-uis.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
