Jest: Contexto legível por humanos para expectativas

Criado em 21 out. 2016  ·  76Comentários  ·  Fonte: facebook/jest

Se houver várias expectativas em um único it , atualmente parece ser impossível descobrir qual expectativa realmente falhou sem fazer referência cruzada da falha com números de linha em seu código.

test('api works', () => {
    expect(api()).toEqual([]) // api without magic provides no items
    expect(api(0)).toEqual([]) // api with zero magic also provides no items
    expect(api(true)).toEqual([1,2,3]) // api with magic enabled provides all items
})

Qual expectativa falhou? A primeira ou a segunda?

image

Seria bom se houvesse algum contexto legível por humanos que deixasse imediatamente claro qual expectativa falhou e o que a saída da expectativa realmente significa em termos humanos, sem ter que encontrar o número da linha no topo do rastreamento de pilha e mapeá-lo de volta para o código.


Compare o tape equivalente abaixo. Ignore que a fita não falha após a primeira falha de asserção. tape imprime uma mensagem legível por humanos acima de cada falha esperada, permitindo que você saiba exatamente qual teste falhou sem voltar ao arquivo de teste.

Observe que isso também empurra o ruído legível para o final da linha na fonte de teste, onde você pode escrever um comentário de qualquer maneira.

test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
})

image


Parece que a única maneira de anexar informações legíveis por humanos a erros com jest é envolver tudo em um it adicional, que é IMO desnecessariamente detalhado.

describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Idealmente, pode-se anexar algum contexto legível por humanos no final do expect alguma forma.

por exemplo

Mensagem de contexto como parâmetro opcional adicional para métodos de asserção:

test('api works', () => {
    expect(api()).toEqual([], 'api without magic provides no items')
    expect(api(0)).toEqual([], 'api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3], 'api with magic enabled provides all items')
})


Ou mensagem de contexto como .because ou .why ou .comment ou .t ou algo assim:

test('api works', () => {
    expect(api()).toEqual([]).because('api without magic provides no items')
    expect(api(0)).toEqual([]).because('api with zero magic provides no items')
    expect(api(true)).toEqual([1,2,3]).because('api with magic enabled provides all items')
})

Alternativamente, seria ainda melhor, talvez, se o jest pudesse simplesmente ler o arquivo e imprimir a linha de código-fonte real em que a própria expectativa está.

Comentários muito úteis

A partir desta discussão e deste repositório , acho que uma boa e semântica seria:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

O uso é semelhante ao because , mas faz mais sentido semanticamente.

Se você gostar disso, talvez eu consiga elaborar um PR adicionando a funcionalidade since .

Todos 76 comentários

Ei! Então, na verdade, costumávamos ter isso no Jasmine, mas descobrimos que em milhares de arquivos de teste no FB, ninguém o usava. Então, por enquanto, estamos imprimindo uma bela mensagem de erro com informações aproximadas e um rastreamento de pilha que levará à expectativa (assim como na sua captura de tela). Concordo que poderíamos imprimir a linha que lança, mas muitas vezes a afirmação tem várias linhas:

expect(a).toEqual({
  …
});

então isso não pareceria tão bom e teríamos que usar um analisador para analisar o JS e extrair as informações relevantes (e recolher linhas longas) ou algo semelhante para torná-lo bonito.

Pessoalmente, acho que estamos mostrando informações suficientes por enquanto, mas estamos felizes em reconsiderar. Se você tiver ideias para algo que não é muito complexo, mas adiciona mais contexto que ajuda a resolver problemas mais rapidamente, entre em contato.

na verdade, costumávamos ter isso no Jasmine, mas descobrimos que em milhares de arquivos de teste no FB, ninguém o usava

@cpojer então o padrão é envolver cada afirmação em um it ? e/ou apenas confiar nos números das linhas?

É possível que esse padrão tenha sido adotado menos por ser melhor ou pior, mas mais apenas por consistência com os testes existentes? ou talvez não saber que o recurso existe? Eu não sabia que isso era em Jasmine.

Concordo que poderíamos imprimir a linha que lança, mas muitas vezes a afirmação tem várias linhas

Refatorar para uma única linha poderia encorajar mais informações semânticas na asserção? Possivelmente?

const adminUser = {
  …
}
expect(a).toEqual(adminUser);

Pessoalmente, acho que estamos mostrando informações suficientes por enquanto, mas feliz em reconsiderar

O exemplo acima mostra que é difícil descobrir exatamente qual afirmação falhou, a menos que você adicione wrappers detalhados (IMO) em tudo. Isso é especialmente verdadeiro em um ambiente transpilado em que os números de linha do mapa de origem nem sempre são precisos. Acredito que entender com rapidez e precisão quebrou e onde é importante, assim como testes concisos.

Se você tiver ideias para algo que não é muito complexo, mas adiciona mais contexto que ajuda a resolver problemas mais rapidamente, entre em contato.

Fiz algumas sugestões acima:

Você está procurando algo mais simples ou diferente?

oi @timoxley! já pensamos em adicionar algo assim.

então o problema com a primeira opção é que alguns matchers têm argumentos opcionais, e isso torna as coisas mais complicadas.

por exemplo, aqui no segundo caso não saberemos se o argumento é uma proximidade ou uma mensagem de erro

expect(555).toBeCloseTo(111, 2, 'reason why');
expect(555).toBeCloseTo(111, 'reason why');

a segunda sugestão não funcionará porque o matcher lançará assim que algo não atender às expectativas

expect(1).toBe(2)/* will throw here */.because('reason');

poderíamos anexar o motivo antes que o matcher seja executado, assim:

expect(1).because('reason').toBe(2);
// or 
because('reason').expect(1).toBe(2);

mas esta API não parece tão boa.

outra opção seria adicionar um segundo argumento a expect

expect(1, 'just because').toBe(2);

mas é praticamente o mesmo que a opção anterior.

A razão pela qual eu acho que isso não é muito útil é porque os engenheiros não querem perder tempo escrevendo testes. Qualquer coisa que fizermos para tornar mais difícil para eles só levará a testes piores.

No passado, a melhor solução era criar matchers personalizados. Apresentaremos expect.extend na próxima versão do Jest e permitirá que você crie facilmente matchers como:

expect(a).toEqualMySpecificThing(…)

que deve permitir que você escreva mensagens de falha mais expressivas. Já vi isso muito usado em projetos como o Relay. Veja todos os matchers: https://github.com/facebook/relay/blob/master/src/tools/__mocks__/RelayTestUtils.js#L281

Fechando por inatividade, mas feliz em reabrir se houver boas ideias.

@cpojer @dmitriiabramov pede desculpas pelo atraso.

Um segundo argumento para expect ou encadear um motivo com .because seria ótimo. O que precisa ser feito para que isso aconteça ou não?

engenheiros não querem perder tempo escrevendo testes

@cpojer Concordo! Além de não querer perder tempo depurando testes, é exatamente por isso que acredito que uma API menos detalhada com mais contexto de falha seria preferível.

Para alguns números concretos, usando o exemplo simples do meu comentário acima, para obter declarações + contexto equivalentes com fita, Jest exige que o programador escreva quase o dobro da quantidade de clichê cerimonial:

  • 1,8x as linhas (6 vs 11)
  • 2x o recuo (1 vs 2)
  • 2x os parênteses/cachos (24 vs 48) !

Isso pode ser melhorado!

// tape
test('api works', t => {
  t.deepEquals(api(), [], 'api without magic provides no items')
  t.deepEquals(api(0), [], 'api with zero magic also provides no items')
  t.deepEquals(api(true), [1,2,3], 'api with magic enabled provides all items')
  t.end()
})

// jest
describe('api works', () => {
  test('api without magic provides no items', () => {
    expect(api()).toEqual([])
  })
  test('api with zero magic also provides no items', () => {
    expect(api(0)).toEqual([])
  })
  test('api with magic enabled provides all items', () => {
    expect(api(true)).toEqual([1,2,3])
  })
})

Atualização : suponho que você possa escrever os testes de jest em uma única linha com setas:

// jest
describe('api works', () => {
  test('api without magic provides no items', () => expect(api()).toEqual([]))
  test('api with zero magic also provides no items', () => expect(api(0)).toEqual([]))
  test('api with magic enabled provides all items', () => expect(api(true)).toEqual([1,2,3]))
})

Isso torna algumas linhas mais longas, mas melhora um pouco as estatísticas que comparamos anteriormente:

  • 0,8x as linhas (6 vs 5)
  • 1x o recuo (1 vs 1)
  • 1,75x os parênteses/cachos (24 vs 42)

No entanto, acho que ter a descrição do teste no início da linha, sem quebra de linha, dificulta a análise visual da lógica porque coloca a "carne" do teste, ou seja, as afirmações reais, em alguma posição arbitrária da coluna.

Analisar o código é mais importante do que ler a descrição do teste, que é basicamente apenas um comentário glorificado. É por isso que ninguém escreve comentários no início da linha. Por exemplo, isso seria uma loucura sadomasoquista:

/* api without magic provides no items */ expect(api()).toEqual([])
/* api with zero magic also provides no items */ expect(api(0)).toEqual([])
/* api with magic enabled provides all items */ expect(api(true)).toEqual([1,2,3])

Idealmente, todo o código de asserção se alinharia perfeitamente na mesma coluna, para que fosse facilmente analisado por um humano. Com base nesse pensamento, eu optaria fortemente pela forma .because à direita em vez da sugestão alternativa de um segundo argumento para expect .

Obrigado por manter a conversa. Observe que você também não precisa do bloco de descrição, tornando as coisas ainda menores. .because infelizmente não funcionará porque quando o matcher lançar (o que acontece antes .because ser chamado), não teremos como extrair o nome.

Use o último argumento de cada função do matcher então?

Só funcionaria se o adicionarmos como um segundo argumento de expect .
Não podemos adicioná-lo como o último argumento para cada função de correspondência por causa da ambiguidade
por exemplo

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

Fechando por inatividade, mas feliz em reabrir se houver boas ideias.

@cpojer não está claro se o caminho jasmim (e outros) de expect(value).toBe(something, 'because message') foi descartado?

Um exemplo é testar coisas redux-saga/redux-observable onde você está testando uma máquina de estado . É muito útil ter uma mensagem descritiva sobre em que estado falhou. Esse exemplo é artificial, então as descrições também são..

@jayphelps não estamos mais usando o jasmim já que reescrevemos todos os matchers de jasmim

@dmitriiabramov desculpe minha pergunta não foi clara. O jasmim _way_ de fazê-lo foi decretado para ser adicionado de volta? Fazendo a mesma coisa que eles permitem.

@jayphelps como eu disse antes, não funcionará para todos os matchers por causa da ambiguidade.

expect(obj).toHaveProperty('a.b.c', 'is that a reason or a value of the property?');

e cantar jest matchers podem ser estendidos com pacotes de terceiros eu não acho que seja uma boa idéia mexer com a lista de argumentos

a opção mais limpa provavelmente é tê-lo como um segundo argumento de expect , já que sempre leva exatamente um argumento.

expect(123, 'jest because').toEqual(123);

não tenho certeza se queremos sobrecarregar a API. Quase nunca o usamos em suítes de teste do facebook e, para casos especiais, acho mais fácil definir um novo teste:

beforeEach(someSharedSetup);
test('reason or description', () => expect(1).toBe(1));

é só mais algumas linhas :)

Ou você pode até colocá-lo em outra chamada describe() .

@dmitriiabramov Os casos irritantes são quando você constrói o estado, como em uma máquina de estado para sagas, épicos, etc. Cada teste requer as mudanças de estado anteriores, isolá-los requer uma tonelada de duplicação sem nenhum ganho AFAIK.

it('stuff', () => {
  const generator = incrementAsync();

  expect(generator.next().value).toBe(
    call(delay, 1000)
  );

  expect(generator.next().value).toBe(
    put({ type: 'INCREMENT' })
  );

  expect(generator.next()).toBe(
    { done: true, value: undefined }
  );
});

Ou você pode até colocá-lo em outra chamada describe().

Você pode elaborar isso? O aninhamento de chamadas de descrição AFAIK era apenas para dividir os títulos das seções, os testes ainda são executados simultaneamente, certo?

As suítes de teste (arquivos) são executadas simultaneamente, as chamadas test() não.

estou começando a pensar que algo como

test('111' () => {
  jest.debug('write something only if it fails');
  expect(1).toBe(2);
});

pode ser uma coisa

A partir desta discussão e deste repositório , acho que uma boa e semântica seria:

it('has all the methods', () => {
  since('cookie is a method').expect(reply.cookie).toBeDefined();
  since('download is a method').expect(reply.download).toBeDefined();
  since('end is a method').expect(reply.end).toBeDefined();
  // ...
});

O uso é semelhante ao because , mas faz mais sentido semanticamente.

Se você gostar disso, talvez eu consiga elaborar um PR adicionando a funcionalidade since .

Por favor, implemente uma maneira fácil de fazer isso. Eu não o uso com muita frequência, mas especialmente para testes mais complicados, é útil saber exatamente o que está falhando sem ter que cavar.

Por favor, não diga "reescreva seus conjuntos de testes para ser mais simples". A única coisa que os engenheiros odeiam mais do que _escrever_ suítes de teste são _reescrever_ suítes de teste.

Outra proposta que vi em algum lugar, esqueci onde estava nesta mesma edição, com uma explicação de por que não funcionaria. Eu provavelmente deveria dormir um pouco :)

Eu tenho uma demonstração simples de "protótipo" funcionando, eu precisaria implementar a recursão agora. É um wrapper fino usando proxies em torno das variáveis ​​globais e depois sobre cada método. No entanto, os proxies não são suportados por navegadores mais antigos e não podem ser preenchidos com polyfilled, portanto, pode não ser aceitável para o Jest. Esta é a estrutura geral para o wrapper:

const since = (text) => {
  return new Proxy(global, {
    get: (orig, key) => {
      return (...args) => {
        try {
          const stack = orig[key](...args);
          return new Proxy(stack, {
            get: (orig, key) => {
              return (...args) => {
                try {
                  const ret = orig[key](...args);

                  // ... implement recursion here

                } catch (err) {
                  console.log('2', key, text, err);
                  throw err;
                }
              }
            }
          });
        } catch (err) {
          console.log('1', key, text, err);
          throw err;
        }
      };
    }
  });
};

Existem três opções realistas:

  • Esta forma é aceitável, portanto, deve ser adicionada à biblioteca principal do Jest. Eu limpo e crio um PR.
  • Aprofunde-se no Jest e modifique a biblioteca principal. Muito trabalho, então não faria nada até que algum(s) membro(s) dissessem algo semi-oficialmente sobre este assunto/direção.
  • Termine-o desta forma e publique-o como um pacote. Indesejável, pois não é facilmente detectável.

Edit: veja em ação:

describe('Test', () => {
  it('works', () => {
    since('It fails!').expect('a').toEqual('b');
  });
});

Você precisa de contexto de expectativa para tornar os resultados dos testes sãos quando você tem testes não triviais. Testes do mundo real nem sempre seriam tão simples.

Lembre-se de correspondências personalizadas - elas ocultam a complexidade matemática. Mas quando o teste falha, ocultar essa complexidade não é o que você deseja, pois deseja obter o máximo de informações sobre a falha. O contexto de expectativa permite que você forneça esse contexto manualmente. Não é o ideal, eu acho, algum tipo de contexto automático seria melhor, mas é a única maneira que eu vi até agora.

Quando eu quebrei algo e ele falha, com Jest eu tenho que depurá-lo manualmente ou adicionar log ou qualquer _modifications._ O que é muito menos conveniente do que apenas olhar para os resultados da execução do teste.
No Jasmine, por exemplo, temos a capacidade de imprimir algum contexto para fazer mais sentido sobre a falha.
No framework de teste JUnit mais popular do Java, também temos exatamente o mesmo recurso.

Desculpe se estou enganado, mas não vejo nenhum contra-argumento _tecnológico_ para esse recurso aqui. E coisas como "isso não deve ser adicionado porque não vai ficar bom" são simplesmente ridículas.

Podemos reabrir? Mesmo jest.debug() como sugerido por @aaronabramov acima seria útil para mim.

This:

it('has all the methods', () => {
    since('cookie is a method', () => expect(reply.cookie).toBeDefined());
});

can be supported by adding this:


// setupTestFrameworkScriptFile.js
// http://facebook.github.io/jest/docs/configuration.html#setuptestframeworkscriptfile-string
global.since = (explanation, fn) => {
    try {
        fn();
    } catch(e) {
        e.message = explanation + '\n' + e.message;
        throw e;
    }
};

Além disso, jasmine-custom-message é semelhante ao que é solicitado:

describe('test', function() {
  it('should be ok', function() {
    since(function() {
      return {'tiger': 'kitty'};
    }).
    expect(3).toEqual(4); // => '{"tiger":"kitty"}'
  });
});

Existem planos para reabrir isso? Parece que houve duplicatas deste problema recentemente. Também estou procurando exibir uma mensagem personalizada quando o teste falhar.

Hmm.. Isso também é algo que eu tenho na minha lista de desejos. Depois de ler este tópico, posso entender a resposta referente ao uso do Jest no Facebook e não querer afetar seu próprio fluxo de trabalho, mas houve algumas sugestões que não interfeririam nos testes existentes e adicionariam a funcionalidade que vários outros gostariam de tenho (inclusive eu).

O que seria necessário para que o 2º argumento espera () ou o formato since () seja aceito como um PR? Estou disposto a doar algum tempo para ajudar com isso.

Acabei de ler o tópico e vejo bons argumentos de ambos os lados. Eu definitivamente quero um mecanismo para fornecer uma mensagem de erro personalizada pelo mesmo motivo que @timoxley postou originalmente. No momento estou fazendo algo assim:

import assert from 'assert'
import chalk from 'chalk'

test('api works', () => {
  assert.deepEqual(
    api(),
    [],
    chalk.red('api without magic provides no items')
  )
  assert.deepEqual(
    api(0),
    [],
    chalk.red('api with zero magic also provides no items')
  )
  assert.deepEqual(
    api(true),
    [1, 2, 3],
    chalk.red('api with magic enabled provides all items')
  )
})

Isso realmente funciona muito bem, mas eu gostaria de evitar ter que usar giz para obter a cor vermelha (impressões sem cor) e eu prefiro que isso seja suportado por expect .

Eu realmente não me importo como isso é implementado honestamente. Mas aqui está uma alternativa para since apenas para lançar outra coisa, caso outros prefiram:

const expectWithMessage = expect.withMessage(
  'api with magic enabled provides all items'
)
expectWithMessage(api(true)).toEqual([1, 2, 3])

// could be rewritten like
expect
  .withMessage('api with magic enabled provides all items')(api(true))
  .toEqual([1, 2, 3])

Não tenho certeza se gosto disso mais do que since . Eu sou bom com qualquer coisa, eu realmente adoraria ter isso :)

Ah, e respondendo ao comentário:

A razão pela qual eu acho que isso não é muito útil é porque os engenheiros não querem perder tempo escrevendo testes. Qualquer coisa que fizermos para tornar mais difícil para eles só levará a testes piores.

Concordo que não queremos tornar mais difícil escrever testes. É por isso que esta seria uma mudança aditiva. Então, as pessoas que não querem "perder tempo" tornando seus testes mais fáceis de depurar, podem simplesmente pular as mensagens úteis, mas chegarão a uma base de código com mensagens úteis como essa e agradecerão ao engenheiro que se deu ao trabalho de explicar um pouco a afirmação :wink:

Oi @cpojer alguma atualização sobre isso?

O Jest está funcionando muito bem para mim, exceto por esse problema ... No momento, estou lutando para corrigir uma declaração com falha em um loop for como
expectationsArray.forEach(expectation => expect(...))

É difícil descobrir exatamente quais expectativas falham sem uma mensagem de erro personalizada (a menos que eu esteja fazendo errado...?)

Obrigada

@mj-airwallex, você é bom para envolver as expectativas com test em um loop for, por exemplo:

const expectationsArray = [[0, 'a'], [1, 'b']];

expectationsArray.forEach(([expectation, desc]) => {
  test(`test ${desc}`, () => {
    expect(expectation).toBeGreaterThanOrEqual(2);
  });
});

Também tenho um problema com o jest devido à necessidade de fornecer mensagens personalizadas durante as expectativas. Agrupar expectativas em test parece funcionar para casos em que não há necessidade de chamadas assíncronas. Mas como o Jest não pode manipular describe (#2235 ) retornando uma promessa, não podemos criar testes com chamadas assíncronas junto com test . E não podemos ter vários test aninhados.

Segue um exemplo para ilustrar o problema:

async function getArray() {
  return [0,0,0,0,0,0]
}

describe('Custom messages with async', async () => {
  const array = await getArray();
  array.forEach((item) => {
    test(`test${item}`, () => {
      expect(item).toBe(0)
    });
  });
})

Alguma idéia de como lidar com isso?

Olhando para o problema no OP ("Seria bom se houvesse algum contexto legível por humanos que deixasse imediatamente claro qual expectativa falhou"), acho que está resolvido agora. A partir de Jest 22, imprimimos o contexto da asserção com falha. A mensagem extra ainda é necessária? Se _is_, pode ser um comentário de código acima ou ao lado da asserção

image

A descrição assíncrona é outro problema (que não será ajudado pelo codeframe adicionado)

Acho que não usaria a descrição assíncrona e, em vez disso, usaria beforeEach ou beforeAll

@kentcdodds você poderia fornecer um exemplo de como lidar com isso com beforeEach ou beforeAll ? Se você tentar criar todos os resultados de chamada assíncrona necessários em beforeEach e beforeAll , no final, você será forçado a criar test aninhados, o que não é permitido.

Ah, eu perdi o que você estava fazendo com essa chamada assíncrona. Desculpe por isso 😅 Sim, você não poderia fazer beforeEach ou beforeAll para fazer isso.

@SimenB , imprimir o contexto já ajuda muito e resolve a maioria dos problemas com mensagens personalizadas. Obrigado por isso! Mas seria bom ter a possibilidade de mensagens personalizadas explicitamente como um argumento, pois isso ajuda em situações como o uso de expectativas dentro de loops.

Olhando para o problema no OP ("Seria bom se houvesse algum contexto legível por humanos que deixasse imediatamente claro qual expectativa falhou"), acho que está resolvido agora.

Sim, isso resolve o problema original, desde que você tenha acesso à fonte original antes da transpilação, que a maioria dos usuários do Jest terá. IMO é um pouco pesado comparado a permitir que os usuários apenas imprimam uma mensagem fornecida pelo usuário com a falha, mas bom o suficiente, eu acho.

Acabei de começar a usar o Jest e estou perdendo um recurso como esse. Meu caso de uso:
Um teste está falhando quando afirmo que uma propriedade de um objeto é verdadeira. Isso me ajudaria a entender a falha mais rapidamente se eu pudesse registrar o objeto caso a asserção falhe.

Você pode usar toHaveProperty para isso.

test('property', () => {
  expect({foo: 'bar'}).toHaveProperty('baz', 'foobar');
});

image

Se você quiser apenas verificar se está lá, descarte o segundo argumento. Se você quiser apenas afirmar que tem _some_ valor, você pode usar expect.anything() .
toMatchObject é outra alternativa.

Você também pode usar assert se quiser.

test('property', () => {
  const obj = {foo: 'bar'};
  assert.equal(obj.baz, 'foobar', JSON.stringify(obj));
});

image

Obrigado pela dica. assert.equal(obj.baz, 'foobar', JSON.stringify(obj)); faria o trabalho no meu caso particular.

@SimenB @mpseidel o que é assert? é alguma biblioteca de terceiros? Não consigo encontrar nada em jest docs.

@sharikovvladislav assert é um módulo principal do nó https://nodejs.org/api/assert.html

@mpseidel opa! Eu não sabia. Obrigada. Funciona.

Estou usando o seguinte fragmento de código para contornar essa limitação da estrutura (no TypeScript, mas apenas remova as anotações de tipo para JS)
export const explain = (expectation: () => void, explanation: string) => { try { expectation(); } catch(e) { console.log(explanation) throw e; } }

Oi,
Estou surpreso que ninguém mencionou loops ainda. A mensagem não seria apenas uma string, mas uma string dinâmica dependendo da iteração do loop.
jest-plugin-context é bom, obrigado por isso funciona, mas é um pouco pesado e o problema inicial ainda é relevante imo.
Veja este teste

describe('MyStuff', () => {
    it('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];
      expectedKeys.forEach((key) => {
        expect(wrapper.find({ id: key }).length).toEqual(1);
      });
    });
  });

Boa sorte para encontrar o culpado. No momento, devo adicionar uma linha ou testar um objeto como {len:.., key:..} , isso não é limpo e não é fácil de usar.
Eu acho que este caso de uso é relevante, para formulários e verificação de renderização de itens, por exemplo.
A sintaxe pode ser tão simples quanto toEqual(1).context("my message") ou toEqual(1, "my message") (embora, é claro, eu saiba que a implementação é sempre mais difícil e respeito o ótimo trabalho que você fez com o Jest).

Talvez use o mesmo formato que chai - ou seja, adicione a mensagem como um segundo argumento para a chamada de espera:

expect(foo, 'this detail').toEqual(2)

T

Jasmine usado antes, então vim aqui para descobrir que não é suportado.
No entanto afik essas coisas são todas as funções. Não podemos simplesmente fazer algo como:

describe('MyStuff', () => {
    describe('should render and contain relevant inputs', () => {
      const wrapper = shallowWrapped(<MyStuff />);
      const expectedKeys = ['love','jest','but','need','details','for','expect'];

      expectedKeys.forEach((key) => {
        it(`contains key "${key}"`, () =>
          expect(wrapper.find({ id: key }).length).toEqual(1)
        )
      })
  });
});

2018-04-18-222246_646x390_scrot

@akkerman Boa solução. Como describe e it são globals mágicos fornecidos por jest, devo admitir que eles podem parecer obscuros, não tinha certeza se escrever ìt` em um loop poderia funcionar.

Que tal encadear outro modificador?

expect(foo).toEqual(bar).because('reason with %s placeholders')

Ou talvez uma função

expect(foo).toEqual(bar).explainedBy((result) => `Lorem ipsum ${result}`)

Acho que outro modificador rapidamente se torna ilegível.
T

2018-04-19 13:47 GMT+02:00 λ • Geovani de Souza [email protected] :

Que tal encadear outro modificador?

expect(foo).toEqual(bar).because('motivo com %s espaços reservados')

Ou talvez uma função

expect(foo).toEqual(bar).explainedBy((resultado) => Lorem ipsum ${result} )


Você está recebendo isso porque comentou.
Responda a este e-mail diretamente, visualize-o no GitHub
https://github.com/facebook/jest/issues/1965#issuecomment-382705387 ou silenciar
o segmento
https://github.com/notifications/unsubscribe-auth/AAM5PwBCvET1KdEDeDEF7gGo708Naj8oks5tqHlSgaJpZM4Kc6Uu
.

--


Tarjei Huse
Móvel: 920 63 413

A maneira expect funciona é jogando, então isso não funcionaria de qualquer maneira.

Acho que expect(something, 'some helpful text on failure').toEqual(somethingElse) ou expect.context(something, 'some helpful text on).toEqual(somethingElse) são as melhores alternativas, mas não gosto muito de nenhum deles

Isso pode ser reaberto? Parece que o Jest ainda não tem uma boa solução para testar como o estado muda em várias interações, por exemplo:

  • testando como um contêiner React com estado muda em resposta a uma série de eventos
  • testando como uma página da web muda ao longo de várias interações usando o Puppeteer

Ambos os casos exigem a execução de uma série de ações e a afirmação de como o estado mudou (ou não mudou) após cada ação, portanto, às vezes, são necessários testes de várias afirmações. Nem sempre é possível resolver esse tipo de problema com beforeEach .

Eu continuo encontrando situações em que isso seria realmente útil. Especificamente quando estou executando várias interações e declarações, como explica @callumlocke .

Se chegarmos a uma API que as pessoas não odeiam, isso é algo que você estaria disposto a buscar? Eu realmente acho que isso seria um recurso valioso e muito usado.

Aqui está um resumo das soluções propostas:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

expect(api(), 'api without magic provides no items').toEqual([])
expect(api()).because('api without magic provides no items').toEqual([])
since('api without magic provides no items').expect(api()).toEqual([]))
because('api without magic provides no items').expect(api()).toEqual([]))
jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

Observe que um .because() à direita não é possível, portanto, não incluído como opção.

Todas as quatro opções do primeiro grupo são suportadas hoje. Pessoalmente, acho que a primeira opção (um quadro de código com um comentário) funciona muito bem. E ainda melhor do que isso é usar um matcher personalizado (opção 4).

Acho que o que precisamos entender para fazer um movimento sobre isso é: o que é mais atraente nas opções do segundo grupo do que nas opções do primeiro? O que o segundo grupo adiciona que pode justificar a manutenção do núcleo para todos os matchers que fornecemos (entre matchers assíncronos, matchers assimétricos, matchers de espionagem, matchers de lançamento, matchers de promessa e matchers personalizados)?

Oi,
Você basicamente tem alguns casos de uso:

  • Testes de várias asserções (você precisa afirmar várias coisas em um teste)
  • Contexto de asserção gerado dinamicamente (você quer uma variável em sua mensagem de falha para torná-la mais clara, por exemplo, para imprimir um campo específico de seu objeto com falha porque você recebeu muitos testes)
  • Asserções geradas dinamicamente (você faz um loop que gera asserções)

As opções do primeiro grupo destinam-se principalmente ao primeiro caso de uso. Se você encontrar a necessidade de asserção gerada dinamicamente, conforme proposto, você pode aninhar chamadas para it e test , para que um teste possa gerar novos testes em um loop. O problema é que você gera testes e não assertivas . Imagine que eu queira afirmar algo em cada elemento de uma matriz de 1000 elementos, isso vai inchar os resumos de teste.

Como esses casos de uso dinâmicos ainda são raros, devemos nos ater à solução que requer trabalho mínimo para os mantenedores. Eu pessoalmente gosto da solução because/since , porque parece bastante simples. Eu acho que a implementação seria principalmente envolver expect em um try/catch que imprime a mensagem e a retorna?
jest.debug soa estranho, para mim depurar é imprimir uma mensagem mesmo que os testes realmente passem
A opção "último argumento" também é boa, mas não tenho certeza se é factível, pois expect pode aceitar um número variável de argumentos?

Estou bem com qualquer opção. Eu só quero o recurso. Também não sou grande na API jest.debug , mas se for a única que faz sentido, estou bem com isso porque só quero esse recurso.

@kentcdodds e as quatro opções existentes?

@eric-burel você viu test.each e describe.each adicionados no Jest 23 (disponível como autônomo para o Jest <23)?

Como eu disse, eu realmente não me importo com qual opção nós vamos. Eu só quero que o recurso exista. Acho que se eu fosse classificá-los por ordem de preferência, seria:

  1. expect(api(), 'api without magic provides no items').toEqual([])
  2. because('api without magic provides no items').expect(api()).toEqual([]))
  3. since('api without magic provides no items').expect(api()).toEqual([]))
  4. expect(api()).because('api without magic provides no items').toEqual([])
  5. jest.debug('api without magic provides no items'); expect(api()).toEqual([]))

(test|describe).each é ótimo, mas não resolve o problema em que você deseja ter várias ações/asserções em um único teste.

O recurso existe hoje com quatro opções:

expect(api()).toEqual([]) // api without magic provides no items
it('api without magic provides no items', () => expect(api()).toEqual([]))
test('api without magic provides no items', () => expect(api()).toEqual([]))
expect(api()).toHaveNoItems()

O que há de errado com estes? As _novas_ soluções propostas parecem ser apenas marginalmente melhores do que as soluções existentes. Que benefícios trazem sobre o que temos que justificam o custo de manutenção?

@rickhanlonii Legal, eu não conhecia test.each , esse é realmente um ótimo recurso, obrigado por apontar isso. Acho que resolve o problema do meu terceiro caso de uso, teste gerado dinamicamente a partir de um array.

Então saiu o segundo que listei: ter uma mensagem de falha gerada dinamicamente, o que tornaria a depuração mais rápida. Eu não tenho muitos casos de uso agora, talvez quando você testar um valor de campo de objeto, você gostaria de imprimir o objeto inteiro em caso de falha. Isso é legítimo imo, como qualquer coisa que torne o teste de escrita mais fácil, mesmo que marginal ou um pouco redundante. Afinal, nós dois podemos escrever it e test , a menos que haja uma diferença que eu não saiba sobre isso é principalmente para conforto.
É marginal, mas realmente algo esperado (sem trocadilhos) pelos usuários, como mostra este tópico.

Edit: criar um teste em outro teste com it ou test e um nome gerado dinamicamente para o teste é uma solução válida, mas eu realmente não gosto de criar um teste quando quero dizer criar uma afirmação . Eu nunca teria imaginado que era possível se a solução não tivesse sido dada neste tópico.

Isso é loucura. Basta adicionar um segundo parâmetro opcional para expect(). Aqueles de nós que querem usá-lo vão (seletivamente), e aqueles que não querem, não vão.

Mocha tem feito isso desde sempre... é uma das razões pelas quais eu abandonei Jasmine anos atrás (a outra sendo muito melhor zombar do timer.) Se eu não tivesse que me juntar ao movimento React, eu não estaria usando Jest ou qualquer outro. outro derivado de jasmim.

Imprimir uma mensagem de erro é uma convenção em tantos outros frameworks de teste e fiquei surpreso por não ver isso no Jest. Encontrei muitos exemplos úteis neste tópico (obrigado por eles), mas adicionar uma maneira explícita de imprimir um erro personalizado na falha do teste seria uma boa adição à usabilidade do Jest. Isso tornaria mais fácil para os desenvolvedores que estão acostumados a outros frameworks de teste (incluindo os não-JS) aumentarem no Jest.

@mattphillips você acha que é possível fazer algo semelhante ao jest-chain aqui para permitir que uma solução exista no userland? Por exemplo, segundo argumento para expect

Honestamente, isso é algo muito padrão na maioria dos frameworks de teste JS. Muito decepcionado por não encontrá-lo no Jest, pois escrevemos todos os nossos testes com uma mensagem de erro personalizada.

@SimenB desculpe, só notei sua mensagem esta manhã!

Sim, isso é possível no userland, acabei de criar e lançar como jest-expect-message https://github.com/mattphillips/jest-expect-message

Feedback bem-vindo :smile:

Incrível, obrigado por fazê-lo!

@cpojer

A razão pela qual eu acho que isso não é muito útil é porque os engenheiros não querem perder tempo escrevendo testes. Qualquer coisa que fizermos para tornar mais difícil para eles só levará a testes piores.

Duas coisas:

  1. Adicionar um segundo argumento opcional ao expect() dificilmente torna as coisas mais difíceis para os desenvolvedores.
  2. A última coisa que os desenvolvedores querem fazer é perder tempo depurando o que causou a falha do teste. Frequentemente, espera x recebido é uma boa maneira de verificar se uma condição foi atendida, mas geralmente não há contexto suficiente sobre o que causou a falha.

Eu usei Mocha/Chai, bem como fita, antes de vir para Jest, e isso é realmente um fator decisivo. O que temos que fazer para obter um suporte de mensagens personalizadas no expect?

Dizer-nos para esperar.extend para criar um matcher personalizado soa exatamente como o que você estava tentando evitar em seu primeiro argumento: "engenheiros não querem perder tempo escrevendo testes".

Acho fácil abrir o teste de unidade e observar o número da linha, para que o caso de uso no OP não me incomode.

O caso de uso que me incomoda é quando tenho um loop dentro de um teste, por exemplo para testar todos os valores de um enum, por exemplo assim:

it("Should contain at least one word of every wordType", () => {
  for (const wordType of wordTypes) {
    expect(words.find((word) => word.wordType === wordType)).toBeTruthy();
  }
});

Se isso falhar, não sei qual valor de wordType falhou.

Minha solução foi substituir isso por uma mensagem que contém o resultado do teste e esperar que a mensagem contenha o resultado do teste esperado (ou seja, verdadeiro). Se falhar, o Jest imprime a mensagem que contém as informações adicionais.

    expect(`${wordType} ${!!words.find((word) => word.wordType === wordType)}`).toEqual(`${wordType} ${true}`);

Jest imprime isso...

expect(received).toEqual(expected)

Difference:

- Expected
+ Received

- 6 true
+ 6 false

... o que me diz que o wordType era 6 quando falhou.

Ou mais legível algo como ...

    if (!words.find((word) => word.wordType === wordType)) {
      expect(`Didn't find a word with wordType '${wordType}'`).toEqual(null);
    }

@cwellsx confira testes parametrizados com test.each desta forma cada wordType será um teste independente com um título descritivo (baseado no valor)

Isso seria incrivelmente útil em testes como este:

test("compare ArrayBufferCursors", () => {
    const orig: ArrayBufferCursor;
    const test: ArrayBufferCursor;

    expect(test.size).toBe(orig.size);

    while (orig.bytes_left) {
        expect(test.u8()).toBe(orig.u8());
    }
});

No momento eu só sei que algum byte no ArrayBufferCursor está errado, mas não tenho ideia de qual. Ser capaz de adicionar um índice como contexto tornaria a depuração muito mais fácil. Felizmente, algumas pessoas apresentaram soluções alternativas aqui, mas todas são feias e lentas.

@rickhanlonii , entendo que você queira reduzir os custos de manutenção do jest, mas as opções que você oferece em seu comentário aumentam a manutenção dos testes unitários de todos os outros projetos. Se eu quiser explicar uma afirmação usando toEqual , que de outra forma é perfeitamente suficiente, você está realmente sugerindo que eu introduza um matcher personalizado?!

Embora existam casos com testes repetitivos, em que it.each são úteis, ter que adicionar um código desnecessário para um simples comentário em uma asserção torna esse framework de teste mais pesado de usar.

Essa discussão e surgimento de jest-expect-message me lembra a questão de beforeAll em Jasmine...

Posso estar entendendo mal a solicitação do OP aqui, mas o problema que eu estava tentando resolver e me trouxe a esse tópico foi resolvido apenas usando um simples try / catch e wrapper em torno jest.expect . Tudo o que eu queria fazer era registrar a totalidade dos objetos que eu esperava versus os que recebi + alguns logs básicos explicando o significado. Claro, isso pode ser estendido para fazer praticamente o que você quiser.

A versão mais genérica disso pode se parecer com:

in some test utility file...

const myExpect = (expectFn, errorCallback) => {
  try {
    expectFn();
  } catch (err) {
    errorCallback();
    throw new Error(err);
  }
};

// in the actual test suite...

const context = { hello: "world", results, expected };
myExpect(
    () => expect(results).toEqual(expected),
    () => console.error("[Error] -- context:", JSON.stringify(context))
);

+1
Oi, eu realmente adoraria esse recurso também. Faria minha vida muito mais fácil.

Esta página foi útil?
0 / 5 - 0 avaliações