da2k / curso-reactjs-ninja

915 stars 322 forks source link

TDD: Como testar uma função que manipula o DOM e não retorna valor? #243

Closed Roger-Melo closed 5 years ago

Roger-Melo commented 5 years ago

Olá, professor!

Preciso testar uma função que irá fazer exatamente isso:

function insertCNHInputsOnForm (markup) {
  const fieldSet = document.querySelector('[data-js="personal-info"]')
  fieldSet.innerHTML += markup
}

Ou seja, já imagino como ela será, mas estou com dificuldades em testá-la, já que ela recebe um parâmetro e não possui retorno. Você pode me dar uma luz?

@fdaciuk

fdaciuk commented 5 years ago

Oi @Roger-Melo! Esse tipo de função é realmente difícil de testar sem preparar todo um ambiente e mockar algumas informações.

O que eu recomendo para testar esse tipo de função é testes E2E (End 2 End), ou testes de tela.

Uma ferramenta ótima pra isso é o Cypress =)

Roger-Melo commented 5 years ago

Boa tarde mestre @fdaciuk !

Bacana, baixei o Cypress aqui e vou começar a usá-lo. Mas como fazer o coverage do Jest dessas funções que serão testadas pelo Cypress alcançar os 100%? Porque, no arquivo de teste, à partir do momento em que é declarado apenas que elas devem ser uma função, o coverage já fica amarelo:

coverage

fdaciuk commented 5 years ago

Não faz! Seu coverage não precisa estar em 100% =)

É importante entender que você está fazendo testes diferentes, para funcionalidades diferentes.

É bem fácil escrever testes unitários para funções puras que só recebem uma entrada, e retornam um valor de saída, e ainda fazer a cobertura de código ficar em 100% para esse caso.

Mas quando começamos a testar funções que causam efeitos colaterais (side effects), como manipulação de DOM, requests, etc., você vai testar a funcionalidade geral, e não vai conseguir, na maioria dos casos, deixar o coverage em 100%.

Não se preocupe, isso é normal! Teste o que for necessário e siga em frente =)

Olhando para uma aplicação na vida real, essa aplicação vai estar sempre recebendo novas atualizações, novas features, ou então melhorias e correções de bugs reportados pelos usuários da aplicação, ou encontrados pelos próprios devs ou QAs.

Para esses casos, você vai escrevendo os testes conforme for encontrando mais situações para testar, saca?

Nem sempre - pra não dizer nunca - você vai conseguir pensar em todas as possibilidades possíveis. Sempre alguma coisa pode ficar pra trás. Por isso o projeto sempre fica em andamento =)

Roger-Melo commented 5 years ago

Entendi, professor.

Escrevi alguns testes com o Cypress, é bem legal mesmo =)

Sobre o Jest, quando adicionei o addEventListener ao input, a seguinte mensagem foi exibida:

jest-message

Depois que adicionei um if verificando se o input é true, os testes voltaram a rodar normalmente. É gambiarra fazer isso?

'use strict'

import moment from 'moment'

const inputBirthday = document.querySelector('[data-js="birthday"]')
const customerDateRegex = /(\d{2})\/(\d{2})\/(\d{4})/

if (inputBirthday) { // IF ADICIONADO
  inputBirthday.addEventListener('keyup', handleBirthDate)
}

export function handleBirthDate () {
  if (isAgeEqualOrGreaterThanEighteen(this.value) && isDateInCorrectFormat(this.value)) {
    insertCNHInputsOnForm('<h1>I\'M HERE!</h1>')
  }
}

export function insertCNHInputsOnForm (markup) {
  const personalInfoFieldset =
    document.querySelector('[data-js="personal-info"]')
  personalInfoFieldset.insertAdjacentHTML('beforeend', markup)
}

export function isAgeEqualOrGreaterThanEighteen (date) {
  const convertedDate = convertDateToMomentJsFormat(date)
  const age = moment().diff(moment(convertedDate, 'YYYYMMDD'), 'years')
  return age >= 18
}

export function isDateInCorrectFormat (date) {
  return customerDateRegex.test(date)
}

export function convertDateToMomentJsFormat (date) {
  return date.replace(customerDateRegex, '$3$2$1')
}

jest-message-2

@fdaciuk

fdaciuk commented 5 years ago

@Roger-Melo se você abrir o arquivo de coverage, vai ver provavelmente que essa linha do if não está entrando (blocos de código são definidos como branch no coverage).

Isso porque você está testando no Node (pelo terminal) uma funcionalidade da API DOM que só existe nos browsers.

Essa função não precisa ser testada no Jest, apenas pelo cypress. Como ela é uma função que vai manipular um evento, faça os side effects necessários nela, e separe as funcionalidades em funções puras.

Assim você consegue testar as funções puras com o Jest, e essas funções de manipulação de evento, você testa com o cypress =)

Roger-Melo commented 5 years ago

Hum... Então o bacana seria seguir os passos abaixo?

@fdaciuk

fdaciuk commented 5 years ago

Isso! Com Jest você ainda pode fazer testes de integração se quiser, pois vc consegue testar requests e retorno de dados com promises nos testes.

Até tem como testar componentes diretamente da linha de comando, usando enzyme ou react-testing-library, por exemplo, mas eu não vejo muito sentido, pois normalmente(*) esses testes vão testar a implementação da biblioteca em si, o que não faz muito sentido, já que a lib já tem testes pra isso =)

No mais, o caminho que eu seguiria é esse mesmo =)

(*) "normalmente" não quer dizer "sempre", pode ser que tenha alguns casos específicos que faça sentido testar usando essas ferramentas, mas, na minha opinião, acho mais eficiente testar como eu comentei acima =)

Roger-Melo commented 5 years ago

Entendi, professor! Muito obrigado pela ajuda.

Estou com uma dúvida sobre eventos, irei abrir uma issue no JS Ninja, pode ser?

fdaciuk commented 5 years ago

Show! Vou ver lá :D