Testa ogiltiga funktionsargument med TypeScript och JestJS
Jag har skapat en Node-applikation med TypeScript och jag använder Jest för att skriva enhetstester.
Mitt problem: Jag kan egentligen inte skriva enhetstester som simulerar ogiltiga funktionsargumenttyper, eftersom TypeScript inte kompilerar. Jag menar, det är bra att TypeScript inser att jag försöker sätta fel data i de funktionerna, men som jag har förstått TS hittills fungerar det bara under kompileringstid. Det kommer inte att finnas några kontroller av argumenttyper under körningstid.
Så jag måste fortfarande testa det korrekta beteendet hos mina IO-beroende funktioner med Jest, eller hur?
Jag tänkte att jag bara skulle skriva en xyz.spec.js och lämna alla gränssnitt och typer bakom för det specifika testet. Men sedan får jag ett fel från jest angående importen av moduler. Jag antar att det är för att det inte är en ts-fil.
Måste jag ändra min Jest- eller TS-inställning för att få det att fungera?
Här är en skärmdump av testet och felet:
här är min package.json:
{
"name": "state",
"version": "0.0.0",
"files": [
"build"
],
"main": "build/index",
"types": "build/index",
"scripts": {
"clean": "rimraf build && rimraf coverage",
"format": "prettier --write \"{src,__tests__}/**/*.ts\" --single-quote --trailing-comma es5",
"lint": "tslint --force --format verbose \"src/**/*.ts\"",
"prepublishOnly": "npm run build",
"start": "node ./build/index.js",
"prebuild": "npm run clean && npm run format && npm run lint && echo Using TypeScript && tsc --version",
"build": "tsc --pretty",
"build:watch": "nodemon --legacy-watch src/index.ts",
"test": "jest --no-cache",
"test:watch": "jest --no-cache --watch",
"coverage": "jest --no-cache --coverage"
},
"dependencies": {
"mongoose": "^5.6.0"
},
"devDependencies": {
"@types/jest": "^24.0.13",
"@types/mongoose": "^5.5.6",
"@types/node": "^10.14.7",
"coveralls": "^3.0.2",
"jest": "^24.8.0",
"nodemon": "^1.19.0",
"prettier": "^1.14.3",
"rimraf": "^2.6.2",
"ts-jest": "^24.0.2",
"ts-node": "^8.1.0",
"tslint": "^5.11.0",
"tslint-config-prettier": "^1.15.0",
"typescript": "^3.1.1"
},
"engines": {
"node": ">=10.0.0"
},
"jest": {
"preset": "ts-jest"
}
}
och här är min tsconfig.json:
{
"compilerOptions": {
"declaration": true,
"module": "commonjs",
"moduleResolution": "node",
"lib": ["esnext"],
"target": "es2015",
"outDir": "./<%= buildpath %>",
"removeComments": true,
"inlineSourceMap": true,
"inlineSources": true,
"preserveConstEnums": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "build"]
}
Med vänliga hälsningar fea
Svar #1
Du kan använda as any i argumentet för ditt test. På så sätt kan du lägga till den "felaktiga" typen och testa din kod med den. Förutsatt att du vill testa someFunction()-funktionen som förväntar sig någon form av Prop-objekt, till exempel
interface Prop {
id: number;
type: string;
}
function someFunction(props: Prop): void {
// ... do some stuff
}
Då skulle ditt test kunna se ut ungefär så här:
it('should not accept the wrong argument type', () => {
const props = { id: 123 };
const testSomeFunction = () => {
someFunction(props as any);
};
expect(testSomeFunction).toThrow();
});
Du kan också ta en titt på Type Guards för att göra typkontroller vid körning: http://www.typescriptlang.org/docs/handbook/advanced-types.html#type-guards-and-differentiating-types
function isValidProp(prop: Prop): prop is Prop {
const keys = ['id', 'type'];
return Object.keys(prop).every(key => keys.includes(key));
}
function someFunction(props: Prop): void {
if(!isValidProp(prop)){
throw new Error('invalid prop!');
}
// ... do some stuff
}
Svar #2
Detta är vad @ts-expect-error är till för. Det talar om för TypeScript att undertrycka följande fel så att du kan skriva tester som annars inte skulle kompilera. Till skillnad från @ts-ignore genererar det också ett fel om följande sats faktiskt inte producerar ett fel. Här är ett exempel:
class NotANumberError extends Error {
constructor(msg: string) {
super(msg);
this.name = 'NotANumberError';
}
}
function acceptsNumber(x: number): void {
if (typeof x !== 'number') {
throw new NotANumberError(`expected number, received ${x}`);
}
}
describe('acceptsNumber', () => {
it('throws NotANumberError when called with string', () => {
// @ts-expect-error test passing invalid string argument
expect(() => acceptsNumber('hello')).toThrow(NotANumberError);
});
});
Koden acceptsNumber('hello') skulle normalt sett inte kompilera, men det felet undertrycks av kommentaren @ts-expect-error ovanför, vilket gör att vi kan köra och testa koden även när typerna inte matchar.
