O objetivo deste projeto é criar um projeto TypeScript que possa fazer o seguinte:
jest
e ts-jest
para teste Começo inicializando isso como um projeto npm
.
$ yarn init .
Então, instalo typescript
, jest
, ts-jest
e @types/jest
como dependências:
$ yarn add -D typescript jest ts-jest @types/jest
No momento em que este artigo foi escrito, isso significava [email protected]
, [email protected]
e [email protected]
.
A seguir, inicializamos isso como um projeto TypeScript usando:
$ npx tsc --init .
Quero que meu código gerado pelo TypeScript seja armazenado em ./lib
e quero que as declarações sejam geradas. Então, eu configuro outDir
em tsconfig.json
como ./lib
.
Meu .gitignore
é então configurado para ser:
/node_modules
/lib
... enquanto meu .npmignore
é apenas:
/node_modules
Pelo mesmo motivo, removo o valor padrão dos files
em tsconfig.json
e substituo por:
"exclude" : [ " node_modules " , " lib " ]
Para começar, crio um src/index.ts
que contém uma função simples:
export function sampleFunction ( x : string ) : string {
return x + x ;
}
Também adiciono um teste simples jest
. Prefiro manter meus testes em um local completamente separado, por isso colocarei todos os meus testes em __tests__
. Então eu crio o seguinte caso de teste em __tests__/base.spec.ts
:
import { sampleFunction } from "../src" ;
describe ( "This is a simple test" , ( ) => {
test ( "Check the sampleFunction function" , ( ) => {
expect ( sampleFunction ( "hello" ) ) . toEqual ( "hellohello" ) ;
} ) ;
} ) ;
Neste ponto, gostaria de executar esse teste. Mas primeiro preciso criar um arquivo jest.config.js
para todas as minhas configurações jest
. Isso deve levar em consideração o fato de estar usando ts-jest
e o fato de meus testes estarem armazenados em __tests__
. Portanto, o arquivo resultante fica assim:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
} ;
Em seguida, adiciono os seguintes scripts a package.json
:
"scripts" : {
"compile" : " tsc " ,
"test" : " jest "
}
Neste ponto, se eu executar yarn test
, obtenho exatamente o que esperava:
PASS __tests__/base.spec.ts
This is a simple test
✓ Check the sampleFunction function (3ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Para ativar a cobertura de código, atualizo meu arquivo jest.config.js
para:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
collectCoverage : true ,
} ;
Também desejarei atualizar meus arquivos .gitignore
e .npmignore
para evitar o controle de versão ou a publicação do diretório coverage
gerado por jest
.
Neste ponto, vou começar a introduzir submódulos em meu projeto. Então, adicionarei um módulo src/core
e um src/utils
apenas para tornar as coisas um pouco mais realistas. Em seguida, exportarei o conteúdo de ambos para que src/index.ts
fique assim:
export * from "./core" ;
export * from "./utils" ;
Em seguida, eles importam arquivos específicos contendo vários tipos e funções. Inicialmente, criarei um conjunto muito simples de tipos para representar expressões extremamente simples apenas com literais e as operações binárias +
, -
, *
e /
. Então posso escrever alguns testes como estes:
import { evaluate , Expression } from "../src" ;
describe ( "Simple expression tests" , ( ) => {
test ( "Check literal value" , ( ) => {
expect ( evaluate ( { type : "literal" , value : 5 } ) ) . toBeCloseTo ( 5 ) ;
} ) ;
test ( "Check addition" , ( ) => {
let expr : Expression = {
type : "binary" ,
operator : "+" ,
left : {
type : "literal" ,
value : 5 ,
} ,
right : {
type : "literal" ,
value : 10 ,
} ,
} ;
expect ( evaluate ( expr ) ) . toBeCloseTo ( 15 ) ;
} ) ;
} ) ;
Até agora tudo bem. Mas observe que se eu realmente executar esses testes, obterei estes resultados:
PASS __tests__/base.spec.ts
Simple expression tests
✓ Check literal value (4ms)
✓ Check addition
Test Suites: 1 passed, 1 total
Tests: 2 passed, 2 total
Snapshots: 0 total
Time: 2.048s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 66.67 | 37.5 | 50 | 66.67 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 61.54 | 37.5 | 100 | 61.54 | |
functions.ts | 54.55 | 37.5 | 100 | 54.55 | 14,16,18,20,25 |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 66.67 | 100 | 0 | 66.67 | |
checks.ts | 50 | 100 | 0 | 50 | 2 |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Observe a falta de cobertura de código. Adicionando mais alguns casos de teste junto com alguns comentários /* istanbul ignore ... */
para que istanbul
saiba o que pode ignorar com segurança, chegamos a:
PASS __tests__/base.spec.ts
Simple expression tests
✓ Check literal value (3ms)
✓ Check addition
✓ Check subtraction
✓ Check multiplication (1ms)
✓ Check division
Test Suites: 1 passed, 1 total
Tests: 5 passed, 5 total
Snapshots: 0 total
Time: 1.353s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 100 | 100 | 100 | 100 | |
functions.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 100 | 100 | 100 | 100 | |
checks.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Agora, se alterarmos um teste para que ele falhe, obteremos algo assim:
● Simple expression tests › Check division
expect(received).toBeCloseTo(expected, precision)
Expected value to be close to (with 2-digit precision):
1
Received:
2
19 | test("Check division", () => {
20 | let expr = bin("/", 10, 5);
> 21 | expect(evaluate(expr)).toBeCloseTo(1);
22 | });
23 | });
24 |
at Object.<anonymous> (__tests__/base.spec.ts:21:32)
Test Suites: 1 failed, 1 total
Tests: 1 failed, 4 passed, 5 total
Snapshots: 0 total
Time: 1.535s
Ran all test suites.
---------------|----------|----------|----------|----------|----------------|
File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines |
---------------|----------|----------|----------|----------|----------------|
All files | 100 | 100 | 100 | 100 | |
src | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/core | 100 | 100 | 100 | 100 | |
functions.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
src/utils | 100 | 100 | 100 | 100 | |
checks.ts | 100 | 100 | 100 | 100 | |
index.ts | 100 | 100 | 100 | 100 | |
---------------|----------|----------|----------|----------|----------------|
Observe que o rastreamento da pilha está correto. Ele aponta para o problema no código TypeScript .
Lembre-se de que adicionamos um script compile
ao nosso package.json
. Podemos compilar o código com yarn compile
. Fazendo isso, vemos que o diretório lib
é preenchido com dois subdiretórios, src
e __tests__
.
Porém, se olharmos nesses diretórios, descobriremos que eles incluem apenas o código Javascript gerado. Eles não incluem definições de tipo. Para gerar definições de tipo (arquivos .d.ts
) para que outros usuários TypeScript possam se beneficiar de todas as informações de tipo que adicionamos ao nosso código, temos que definir o campo declaration
em nosso arquivo tsconfig.json
como true
.
Observe também que para que outros possam usar este pacote como um módulo NPM, você precisa definir o campo main
em package.json
como lib/src/index.js
. Além disso, para que outros possam acessar os tipos neste módulo, também precisamos definir o campo typings
em package.json
como lib/src/index.d.ts
. Em outras palavras,
"main" : " lib/src/index.js " ,
"typings" : " lib/src/index.d.ts " ,
Se configurado corretamente, podemos iniciar uma sessão node
e importar nosso novo pacote:
$ node
> var me = require( " . " )
undefined
> me
{ evaluate: [Function: evaluate],
assertNever: [Function: assertNever] }
>
Agora certifique-se de atualizar seu jest.config.js
para incluir a seguinte configuração ou jest
começará a corresponder ao código no diretório lib/__tests__
:
testPathIgnorePatterns: ["/lib/", "/node_modules/"],
Finalmente, chegamos à depuração. Estou usando o Visual Studio Code, então demonstrarei como fazer a depuração funcionar lá. Algumas dessas informações podem muito bem ser traduzidas para outros IDEs.
No VSCode, podemos ir para a barra lateral de depuração. Inicialmente, ao lado do botão “play” estarão as palavras “Sem Configuração”. Clicar nele abre um menu suspenso com a opção "Adicionar configuração...".
Por mais que eu ame TypeScript, a depuração é realmente o seu calcanhar de Aquiles. Não é que você não possa depurar, é apenas difícil começar a trabalhar. Se você selecionar "Adicionar configuração..." e depois "Node.js", verá várias pré-configurações, incluindo uma para mocha
. Mas não existe para jest
. Então você terá que criar seu próprio arquivo .vscode/launch.json
. Felizmente, a página de jest
sugere que você crie um arquivo .vscode/launch.json
parecido com este:
{
"version" : " 0.2.0 " ,
"configurations" : [
{
"name" : " Debug Jest Tests " ,
"type" : " node " ,
"request" : " launch " ,
"runtimeArgs" : [ " --inspect-brk " , " ${workspaceRoot}/node_modules/.bin/jest " , " --runInBand " ],
"console" : " integratedTerminal " ,
"internalConsoleOptions" : " neverOpen "
}
]
}
Fiquei agradavelmente surpreso ao descobrir que poderia não apenas executar meus testes e obter cobertura de código como de costume, mas também definir pontos de interrupção nos testes ( ou seja, em __tests__/base.spec.ts
), bem como no código ( por exemplo, src/core/functions.ts
) e o depurador os encontrará.
Observe que testei tudo isso no Node 8.x. Já vi problemas com a depuração usando o Node 6.x, então se você estiver tendo problemas, considere atualizar (ou deixe, se conseguir consertar, envie um PR para este README explicando a correção).