> 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-para-colaboradores/testing-standards/testing-uis.md).

# Testar UIs

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

## Pilha de testes

Todos os nossos testes de UI DEVEM ser feitos usando [Jest](https://jestjs.io/) como o principal framework de testes com [redux-saga-test-plan](https://github.com/jfairbank/redux-saga-test-plan) como a ferramenta de testes de sagas e [React Testing Library](https://testing-library.com/docs/react-testing-library/intro/) como a ferramenta de testes de componentes de UI. O código do teste 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 de UI
* Sagas
* Reducers
* Selectors
* Criadores de ações
* Funções utilitárias

## Testando sagas

As Sagas nos permitem lidar com efeitos colaterais, permitindo-nos criar geradores que tratam ações redux. Uma das principais vantagens das sagas é sua testabilidade, já que os effects 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) passando por toda a execução dos handlers que reagirão à action que vamos disparar. Essa abordagem torna os testes de sagas fáceis de construir e manter, e agrega valor aos testes por percorrerem o máximo possível de todo o fluxo.

Os testes dos handlers DEVEM ser feitos por meio de sua interação com os effects, principalmente com o `put` já que sua finalidade é tratar actions e produzir novas. A maioria dos testes certamente conterá um `provide` call, usado para simular effects, um ou vários `put` calls, para verificar se os handlers dispatcham as actions como esperado e um único `dispatch` seguido por um `run` call para executar todas as sagas até a conclusão.

A simulação de recursos externos ao módulo que está sendo testado DEVE ser feita usando os providers do redux-saga-test-plan, aproveitando os diferentes effects fornecidos por redux-sagas. Effects como call, apply ou outros DEVEM ser usados sempre que possível quando uma função é chamada em um teste, para permitir simulá-las facilmente. Jest NÃO DEVE ser usado para simular módulos ou funções.

O texto em describe e seus DEVEM 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 action (ou da intenção da action) *quando estiver lidando com a action que sinaliza uma busca bem-sucedida.*
* Os describes internos diferenciarão contextos de execução, e seguem as mesmas regras descritas na [seção Descrevendo e construindo contexto](/contributor/contributor-pt/guias-para-colaboradores/testing-standards/writing-tests.md#describing-and-building-context) .
* O **it**s DEVEM descrever o que se espera do teste, começando com um *should put* e a descrição das actions que devem ser colocadas ao executar o handler.

### Exemplo de Sagas

```tsx
...
export function* tiersSaga(builder: BuilderAPI) {
	function* handleBuyThirdPartyItemTierRequest(action: BuyThirdPartyItemTiersRequestAction) {
    const { thirdParty, tier } = action.payload
    try {
			// Obter o chain id pode falhar, um teste deve ser feito para garantir que este cenário seja exercitado
      const maticChainId: ChainId = yield call(getChainIdByNetwork, Network.MATIC)
      const thirdPartyContract = yield call(getContract, ContractName.ThirdPartyRegistry, maticChainId)
			// Enviar a transação pode resultar em sucesso ou falha; ambos os casos devem ser exercitados em um teste
      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
// O describe principal começa com 'when handling...'
describe('when handling the request to buy a third party item tier', () => {
	// Todos os casos, negativos e positivos, são exercitados
  describe('and the chain id couldn\'t be retrieved', () => {
    it('deve colocar a action que sinaliza a falha da compra de um tier de item com o tier e o third party id', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
				// provide calls simulam os diferentes effects, construindo diferentes cenários
        .provide([[call(getChainIdByNetwork, Network.MATIC), Promise.reject(new Error(defaultError))]])
				// put calls verificam que o que era esperado como resultado final aconteceu
        .put(buyThirdPartyItemTiersFailure(defaultError, thirdParty.id, thirdPartyItemTier))
				// Um único dispatch por teste é feito para exercitar o tratamento da action que estamos testando
        .dispatch(buyThirdPartyItemTiersRequest(thirdParty, thirdPartyItemTier))
        .run({ silenceTimeout: true })
    })
  })

  describe('and sending the transaction fails', () => {
    it('deve colocar a action que sinaliza a falha da compra de um tier de item com o tier e o third party id', () => {
      return expectSaga(tiersSaga, mockedBuilderApi)
        .provide([
					// call corresponde ao efeito call exato, levando em consideração os parâmetros
					// Isso faz com que nosso teste garanta que o código executado antes de um put fez o que esperávamos
          [call(getChainIdByNetwork, Network.MATIC), ChainId.MATIC_MUMBAI],
          [call(getContract, ContractName.ThirdPartyRegistry, ChainId.MATIC_MUMBAI), contract],
					// Matchers são usados para corresponder apenas à função e não aos parâmetros
          [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('deve colocar a action que sinaliza a compra bem-sucedida de um tier de item com o tier, o third party e os detalhes da transação', () => {
      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 mudanças programadas para ocorrer para a action 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 o state retornado por completo, já que uma action pode modificar qualquer parte do state inicial dado.

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:

* Os describes principais DEVEM começar descrevendo quais actions serão reduzidas usando a frase *when reducing the action* seguida da descrição da action. NÃO DEVEMOS usar o type da action para descrever a action a ser testada, pois o type pode mudar.
* O **it**s DEVEM descrever a forma como o state retornado mudou de maneira clara. Ex.: *deve retornar um state com o erro nulo e os fruits definidos*.

### 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(() => {
		// Um erro é definido para garantir que ele seja limpo ao reduzir a action.
		state.error = 'anError'
	})

	it('should return a state where the error is cleared and the loading state is set to loading', () => {
		// Action creators são usados para testar o reducer para que ele possa ser mantido com facilidade.
		const action = fetchFruitsRequest()

		expect(fruitsReducer(state, action)).toEqual({
			...state,
			error: null,
			/* Como o loading reducer faz parte de outro reducer (e não precisa ser testado),
				 podemos optar por usá-lo como está ou podemos usar um helper que o emule.
			*/
			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, memoized selectors e selectors normais ou comuns. Selectors normais ou comuns DEVEM ser testados como qualquer outra função testada unitariamente e selectors memoized DEVEM ser testados usando o `resultFunc` ou a função que recebe as partes memoized do state e retorna o valor desejado.

**Describe**s e **it**s DEVEM ser escritos conforme descrito na [seção Descrevendo e construindo contexto](/contributor/contributor-pt/guias-para-colaboradores/testing-standards/writing-tests.md#describing-and-building-context) .

### 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]
})

// Os selectors comuns são testados unitariamente como funções normais
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 actions que depois 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 na [seção Descrevendo e construindo contexto](/contributor/contributor-pt/guias-para-colaboradores/testing-standards/writing-tests.md#describing-and-building-context) .

### Exemplo de action creator

```tsx
export const BUY_THIRD_PARTY_ITEM_TIERS_REQUEST = '[Request] Comprar um tier de item de terceiro'
export const BUY_THIRD_PARTY_ITEM_TIERS_SUCCESS = '[Success] Comprar um tier de item de terceiro'
export const BUY_THIRD_PARTY_ITEM_TIERS_FAILURE = '[Failure] Comprar um tier de item de terceiro'

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 de UI

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

### O que testar

O objetivo deste teste de componentes de UI é testar o comportamento do componente e como o usuário interage com ele. Em muitos casos pode haver componentes conectados ao Redux store para obter algumas propriedades e chamar algumas actions. Geralmente lidamos com isso criando um `container` arquivo que interagirá com o store. Esses arquivos estão fora do escopo para esse tipo de teste. Estamos testando como o componente se comporta com diferentes valores de props; não devemos nos importar com a origem desses valores (Redux store, context, componente pai), mas com o impacto deles na renderização final.

### Renderização

Todo teste deve começar chamando a função render. Isso 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 isso pode causar problemas fazendo com que a árvore do DOM tenha mais componentes do que queremos. Devemos salvar o resultado em uma propriedade nomeada **screen**. Fazer isso nos permitirá obter todas as propriedades de que precisamos no teste sem a necessidade de mudar constantemente a desconstrução.

```jsx
it('deve renderizar o componente hello world', () => {
	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 ficará responsável por 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}
    />
  )
}
```

### Consultas

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

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

### Testando eventos do usuário

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

```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 a função `debug` que é uma das propriedades retornadas ao chamar render

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

debug()
```

### Exemplo completo de teste de componente de 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 junto aos arquivos ou módulos que irão exercitar, com o mesmo nome, mas com a extensão `spec.ts` em vez de `ts`.

A estrutura de arquivos DEVE ser 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 by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

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.
