El objetivo de este proyecto es crear un proyecto TypeScript que pueda hacer todo lo siguiente:
jest
y ts-jest
para realizar pruebas Empiezo iniciando esto como un proyecto npm
.
$ yarn init .
Luego, instalo typescript
, jest
, ts-jest
y @types/jest
como dependencias:
$ yarn add -D typescript jest ts-jest @types/jest
Al momento de escribir este artículo, eso significa [email protected]
, [email protected]
y [email protected]
.
A continuación, inicializamos esto como un proyecto TypeScript usando:
$ npx tsc --init .
Quiero que mi código generado por TypeScript se almacene en ./lib
y quiero que se generen declaraciones. Entonces, configuro outDir
en tsconfig.json
para que sea ./lib
.
Mi .gitignore
se configura entonces para ser:
/node_modules
/lib
... mientras que mi .npmignore
es simplemente:
/node_modules
Por la misma razón, elimino el valor predeterminado para files
en tsconfig.json
y lo reemplazo por:
"exclude" : [ " node_modules " , " lib " ]
Para comenzar, creo un src/index.ts
que contiene una función simple:
export function sampleFunction ( x : string ) : string {
return x + x ;
}
También agrego una prueba jest
simple. Prefiero mantener mis pruebas en una ubicación completamente separada, así que las colocaré todas en __tests__
. Entonces creo el siguiente caso de prueba en __tests__/base.spec.ts
:
import { sampleFunction } from "../src" ;
describe ( "This is a simple test" , ( ) => {
test ( "Check the sampleFunction function" , ( ) => {
expect ( sampleFunction ( "hello" ) ) . toEqual ( "hellohello" ) ;
} ) ;
} ) ;
En este punto, me gustaría realizar esa prueba. Pero primero necesito crear un archivo jest.config.js
para todas mis configuraciones jest
. Esto debe tener en cuenta el hecho de que estoy usando ts-jest
y el hecho de que mis pruebas están almacenadas en __tests__
. Entonces el archivo resultante se ve así:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
} ;
Luego agrego los siguientes scripts a package.json
:
"scripts" : {
"compile" : " tsc " ,
"test" : " jest "
}
En este punto, si ejecuto yarn test
, obtengo exactamente lo que esperaba:
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 habilitar la cobertura del código, actualizo mi archivo jest.config.js
a:
module . exports = {
transform : {
"^.+\.tsx?$" : "ts-jest" ,
} ,
testRegex : "(/__tests__/.*|(\.|/)(test|spec))\.(jsx?|tsx?)$" ,
moduleFileExtensions : [ "ts" , "tsx" , "js" , "jsx" , "json" , "node" ] ,
collectCoverage : true ,
} ;
También querré actualizar mis archivos .gitignore
y .npmignore
para evitar el control de versiones o la publicación del directorio coverage
generado por jest
.
En este punto, comenzaré a introducir submódulos en mi proyecto. Así que agregaré un módulo src/core
y src/utils
para que las cosas sean un poco más realistas. Luego exportaré el contenido de ambos para que src/index.ts
se vea así:
export * from "./core" ;
export * from "./utils" ;
Luego importan archivos específicos que contienen varios tipos y funciones. Inicialmente, crearé un conjunto de tipos muy simple para representar expresiones extremadamente simples con solo literales y operaciones binarias +
, -
, *
y /
. Entonces puedo escribir algunas pruebas como estas:
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 ) ;
} ) ;
} ) ;
Hasta ahora, todo bien. Pero tenga en cuenta que si realmente ejecuto estas pruebas, obtengo estos 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 | |
---------------|----------|----------|----------|----------|----------------|
Tenga en cuenta la falta de cobertura del código. Al agregar algunos casos de prueba más junto con algunos comentarios /* istanbul ignore ... */
para que istanbul
sepa qué puede ignorar con seguridad, llegamos 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 | |
---------------|----------|----------|----------|----------|----------------|
Ahora, si cambiamos una prueba para que falle, obtenemos algo como esto:
● 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 | |
---------------|----------|----------|----------|----------|----------------|
Tenga en cuenta que el seguimiento de la pila es correcto. Señala el problema en el código TypeScript .
Recuerde que agregamos un script compile
a nuestro package.json
. Podemos compilar el código con yarn compile
. Al hacerlo, vemos que el directorio lib
está lleno de dos subdirectorios, src
y __tests__
.
Sin embargo, si miramos en esos directorios, encontraremos que solo incluyen el código Javascript generado. No incluyen definiciones de tipos. Para generar definiciones de tipo (archivos .d.ts
) para que otros usuarios de TypeScript puedan beneficiarse de toda la información de tipo que hemos agregado a nuestro código, debemos configurar el campo declaration
en nuestro archivo tsconfig.json
como true
.
También tenga en cuenta que para que otros utilicen este paquete como módulo NPM, debe configurar el campo main
en package.json
en lib/src/index.js
. Además, para que otros puedan acceder a los tipos de este módulo, también debemos configurar el campo typings
en package.json
en lib/src/index.d.ts
. En otras palabras,
"main" : " lib/src/index.js " ,
"typings" : " lib/src/index.d.ts " ,
Si está configurado correctamente, podemos iniciar una sesión node
e importar nuestro nuevo paquete:
$ node
> var me = require( " . " )
undefined
> me
{ evaluate: [Function: evaluate],
assertNever: [Function: assertNever] }
>
Ahora asegúrese de actualizar su jest.config.js
para incluir la siguiente configuración o jest
comenzará a coincidir con el código en el directorio lib/__tests__
:
testPathIgnorePatterns: ["/lib/", "/node_modules/"],
Finalmente, llegamos a la depuración. Estoy usando Visual Studio Code, así que demostraré cómo hacer que la depuración funcione allí. Es muy posible que parte de esta información se traduzca a otros IDE.
En VSCode, podemos ir a la barra lateral de depuración. Inicialmente, junto al botón "reproducir" estarán las palabras "Sin configuración". Al hacer clic en él, aparece un menú desplegable con la opción "Agregar configuración...".
Por mucho que me guste TypeScript, la depuración es realmente su talón de Aquiles. No es que no se pueda depurar, es que simplemente es difícil ponerse a trabajar. Si selecciona "Agregar configuración..." y luego "Node.js", verá varias configuraciones previas, incluida una para mocha
. Pero no hay nadie para jest
. Entonces tendrás que crear tu propio archivo .vscode/launch.json
. Afortunadamente, la página jest
sugiere que cree un archivo .vscode/launch.json
con este aspecto:
{
"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 "
}
]
}
Me sorprendió gratamente descubrir que no sólo podía ejecutar mis pruebas y obtener cobertura de código como de costumbre, sino también establecer puntos de interrupción tanto en las pruebas ( es decir, en __tests__/base.spec.ts
) como en el código ( por ejemplo, src/core/functions.ts
) y el depurador los encontrará.
Tenga en cuenta que probé todo esto en el Nodo 8.x. He visto problemas con la depuración usando Node 6.x, por lo que si tiene problemas allí, podría considerar actualizar (o dejar, si logra solucionarlo, enviar un PR para este README que explica la solución).