I've beend creating a node application with typescript and I'm using jest to write unit tests.
My issue: I can't really write unit tests simulating invalid function argument types, because typescript won't compile. I mean it's nice that typescript realises that I try to put wrong data in those functions, but the way I understood ts so far is, it only works during compile time. There won't be any checks of argument types during runtime.
So I still have to test the correct behaviour of my IO depending functions with jest, right?
I thought I'll just write a xyz.spec.js that and leave all the interfaces and types behind for that specific test. But then I receive an error from jest regarding the import of modules. I guess because it's not a ts file.
Do I have to change my jest or ts setup to make it work?
Here is a screenshot of the test and the error:
here my 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"
}
}
and here my 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"]
}
Best regards fea
For TypeScript, unit tests are run against the generated JavaScript code. In most TypeScript scenarios, you can debug a unit test by setting a breakpoint in TypeScript code, right-clicking a test in Test Explorer, and choosing Debug.
Via ts-jest ts-jest is a TypeScript preprocessor with source map support for Jest that lets you use Jest to test projects written in TypeScript.
The type system cannot replace all testing, but there are certainly places where it can make testing superfluous. Increasingly I strive to make more and more of a codebase like that.
You can use as any
in the argument of your test. That way you can add the "wrong" type and test your code with it. Assuming you want to test the someFunction()
function which expects some sort of Prop object, for instance
interface Prop {
id: number;
type: string;
}
function someFunction(props: Prop): void {
// ... do some stuff
}
Then your test could look something like this:
it('should not accept the wrong argument type', () => {
const props = { id: 123 };
const testSomeFunction = () => {
someFunction(props as any);
};
expect(testSomeFunction).toThrow();
});
You could also have a look at Type Guards, to do runtime type checks: 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
}
This is what @ts-expect-error is for. It tells typescript to suppress the following error so that you can write tests that otherwise would not compile. Unlike @ts-ignore, it also generates an error if the following statement does not actually produce an error. Here is an example:
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);
});
});
The acceptsNumber('hello')
would normally not compile, but that error gets suppressed by the @ts-expect-error
comment above it, allowing us to run and test the code even when types mismatch.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With