Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

jest-plugin-must-assert

Package Overview
Dependencies
Maintainers
1
Versions
14
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

jest-plugin-must-assert - npm Package Compare versions

Comparing version 0.0.2 to 1.0.0

6

index.js

@@ -1,1 +0,5 @@

require("./src");
const pathJestAPI = require("./src");
pathJestAPI({
logger: console
});

3

package.json
{
"name": "jest-plugin-must-assert",
"version": "0.0.2",
"version": "1.0.0",
"description": "Jest plugin for async tests",

@@ -27,2 +27,3 @@ "main": "index.js",

"jest": "^24.7.1",
"jest-circus": "^24.7.1",
"jest-cli": "^24.7.1",

@@ -29,0 +30,0 @@ "meow": "^5.0.0",

@@ -6,6 +6,15 @@ # `jest-plugin-must-assert`

## WARNING
## Problem
The plugin is in a working state, but is under construction :construction:
Asynchronous tests could be challenging to get _right_, particulrary for junior
developers or Engineers new to async JavaScript. The most common mistake is an async
test which does not fire any assertions, either due to logic or even syntax errors.
Static analysis(linters) gets close to pointing out the issues, but is not enough to catch logic mistakes.
For this, we require a runtime check that _some_ assertion was ran during the test.
[Jest, unfortunately, has not "failWithoutAssertions" configuration options, so this plugin aims to remedy that.]()
The plugin patches Jest API to force tests without any assertions to fail. In addition
to failing tests without assertions this plugin also patches a bug in Jest which
leads to [assertions "leaking" accross different tests](https://github.com/facebook/jest/issues/8297).
## Install

@@ -17,2 +26,4 @@

## Use
```js

@@ -31,3 +42,18 @@ ...

mustAssert({
message: 'Please see https://jestjs.io/docs/en/asynchronous for details on testing async code.'
/**
* Control the task execution during a test. You may log a custom warning message
* from here, throw an error etc.
*
* Default: The default behavior is that mismatched testIds result in ignoring of the task
* and a warning message.
*
* @param {Number} originTestId The unique ID of the test where the task is oginating from
* @param {Number} currentTestId The unique ID of the currently executing test
* @param {Function} log The log method (logger.warn)
*
* @return {Boolean} true/false for whether or not the task should execute
onInvokeTask(originTestId, currentTestId, log) {
return false;
}
logger,
});

@@ -46,5 +72,6 @@ ```

## Problem
## Performance
Static analysis is not enough to catch mistakes in asynchronous tests. For this, we require a runtime
check that _some_ assertion was ran during the test.
There is some performance implications of using this plugin as it does add a bit of
overhead, but from testing it's a trivial increase. This plugin has been tested
within a project with 1600+ test suites and over 10k individual tests, with only a negligble slow-down.

@@ -1,128 +0,141 @@

const consoleMethods = Object.entries(global.console);
const restoreConsole = () =>
consoleMethods.forEach(([key, value]) => {
global.console[key] = value;
});
module.exports = patchJestAPI;
require('zone.js');
function onInvokeTaskDefault(originZoneId, currentZoneId, log) {
if (originZoneId !== currentZoneId) {
log(
`Test "${current.name}" is attempting to invoke a ${task.type}(${
task.source
}) after test completion. Ignoring`
);
return false;
}
return true;
}
// NOTE: zone.js patches console methods, avoid that.
restoreConsole();
function patchJestAPI({
onInvokeTask = onInvokeTaskDefault,
logger = console,
}) {
const consoleMethods = Object.entries(global.console);
const restoreConsole = () =>
consoleMethods.forEach(([key, value]) => {
global.console[key] = value;
});
// Zone sets itself as a global...
const Zone = global.Zone;
let currentZone = null;
let uniqueIdentifier = 0;
const uuid = () => ++uniqueIdentifier;
require('zone.js');
const testHasNoExplicitAssertionChecks = () => {
// Some misconfigured test (eg overriding expect itself)
if (!(typeof expect !== 'undefined' && 'getState' in expect)) {
return false;
}
const state = expect.getState();
return (
typeof state.expectedAssertionsNumber !== 'number' &&
!state.isExpectingAssertions
);
};
// NOTE: zone.js patches console methods, avoid that.
restoreConsole();
const exitZone = () => (currentZone = null);
const enterZone = (callback, name, hasDoneCallback) => {
const id = uuid();
const zone = Zone.root.fork({
name,
properties: {
id,
},
onHandleError(delegate, current, target, e) {
// Zone sets itself as a global...
const Zone = global.Zone;
let currentZone = null;
let uniqueIdentifier = 0;
const uuid = () => ++uniqueIdentifier;
const testNeedsAssertionCheck = () => {
// Some misconfigured test (eg overriding expect itself)
if (!(typeof expect !== 'undefined' && 'getState' in expect)) {
return false;
},
onInvokeTask(delegate, current, target, task, applyThis, applyArgs) {
if (current.get('id') !== currentZone) {
console.warn(
`Test "${current.name}" is attempting to invoke a ${task.type}(${
task.source
}) after test completion. Ignoring`
);
return;
}
}
const state = expect.getState();
return (
typeof state.expectedAssertionsNumber !== 'number' &&
!state.isExpectingAssertions
);
};
return delegate.invokeTask(target, task, applyThis, applyArgs);
},
});
const exitZone = () => (currentZone = null);
const enterZone = (callback, name, hasDoneCallback) => {
const id = uuid();
const zone = Zone.root.fork({
name,
properties: {
id,
},
onHandleError(delegate, current, target, e) {
return false;
},
onInvokeTask(delegate, current, target, task, applyThis, applyArgs) {
if (!onInvokeTask(current.get('id'), currentZone, logger.warn)) {
return;
}
return delegate.invokeTask(target, task, applyThis, applyArgs);
},
});
currentZone = id;
currentZone = id;
return zone.wrap(hasDoneCallback ? callback : done => callback(done));
};
return zone.wrap(hasDoneCallback ? callback : done => callback(done));
};
const wrapTest = (fn, name) => {
let testMustAssert;
const hasDoneCallback = fn.length > 0;
const wrapTest = (fn, name) => {
let testMustAssert;
const hasDoneCallback = fn.length > 0;
if (!hasDoneCallback) {
return () => {
const result = enterZone(fn, name, false)();
if (!hasDoneCallback) {
return () => {
const result = enterZone(fn, name, false)();
if (testHasNoExplicitAssertionChecks()) {
if (testNeedsAssertionCheck()) {
expect.hasAssertions();
}
if (
typeof result === 'object' &&
result != null &&
typeof result.then === 'function'
) {
return result.then(exitZone, e => {
exitZone();
throw e;
});
}
exitZone();
return result;
};
}
return (doneOriginal, ...args) => {
const done = () => {
exitZone();
doneOriginal();
};
const result = enterZone(fn, name, true)(done, ...args);
if (testNeedsAssertionCheck()) {
expect.hasAssertions();
}
if (
typeof result === 'object' &&
result != null &&
typeof result.then === 'function'
) {
return result.then(exitZone, e => {
exitZone();
throw e;
});
}
exitZone();
return result;
};
}
};
return (doneOriginal, ...args) => {
const done = () => {
exitZone();
doneOriginal();
function enhanceJestImplementationWithAssertionCheck(jestTest) {
return function ehanchedJestMehod(name, fn, timeout) {
return jestTest(name, wrapTest(fn, name), timeout);
};
const result = enterZone(fn, name, true)(done, ...args);
}
if (testHasNoExplicitAssertionChecks()) {
expect.hasAssertions();
// Create the enhanced version of the base test() method
const enhancedTest = enhanceJestImplementationWithAssertionCheck(global.test);
Object.keys(global.test).forEach(key => {
if (
typeof global.test[key] === 'function' &&
key !== 'each' &&
key !== 'skip'
) {
enhancedTest[key] = enhanceJestImplementationWithAssertionCheck(
global.test[key]
);
} else {
enhancedTest[key] = global.test[key];
}
});
return result;
};
};
function enhanceJestImplementationWithAssertionCheck(jestTest) {
return function ehanchedJestMehod(name, fn, timeout) {
return jestTest(name, wrapTest(fn, name), timeout);
};
global.it = enhancedTest;
global.fit = enhancedTest;
global.test = enhancedTest;
}
// Create the enhanced version of the base test() method
const enhancedTest = enhanceJestImplementationWithAssertionCheck(global.test);
Object.keys(global.test).forEach(key => {
if (
typeof global.test[key] === 'function' &&
key !== 'each' &&
key !== 'skip'
) {
enhancedTest[key] = enhanceJestImplementationWithAssertionCheck(
global.test[key]
);
} else {
enhancedTest[key] = global.test[key];
}
});
global.it = enhancedTest;
global.fit = enhancedTest;
global.test = enhancedTest;
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc