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

babel-plugin-mockable-imports

Package Overview
Dependencies
Maintainers
1
Versions
15
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

babel-plugin-mockable-imports - npm Package Compare versions

Comparing version 1.4.0 to 1.5.0

6

CHANGELOG.md

@@ -8,2 +8,8 @@ # Changelog

## [1.5.0] - 2019-05-13
- Support passing a function to `$mock` to semi-automatically mock imports
based on the source, symbol name or original value
[(#14)](https://github.com/robertknight/babel-plugin-mockable-imports/pull/14)
## [1.4.0] - 2019-05-08

@@ -10,0 +16,0 @@

137

index.js

@@ -1,6 +0,6 @@

'use strict';
"use strict";
const pathModule = require('path');
const pathModule = require("path");
const packageName = require('./package.json').name;
const packageName = require("./package.json").name;
const helperImportPath = `${packageName}/lib/helpers`;

@@ -16,3 +16,3 @@

// here breaks the plugin.
'proxyquire',
"proxyquire"
];

@@ -27,5 +27,5 @@

*/
const EXCLUDED_DIRS = ['test', '__tests__'];
const EXCLUDED_DIRS = ["test", "__tests__"];
module.exports = ({types: t}) => {
module.exports = ({ types: t }) => {
/**

@@ -38,3 +38,3 @@ * Return the required module from a CallExpression, if it is a `require(...)`

if (
node.callee.name === 'require' &&
node.callee.name === "require" &&
args.length === 1 &&

@@ -54,3 +54,3 @@ t.isStringLiteral(args[0])

return t.callExpression(
t.memberExpression(t.identifier('$imports'), t.identifier('$add')),
t.memberExpression(t.identifier("$imports"), t.identifier("$add")),
[

@@ -60,4 +60,4 @@ t.stringLiteral(alias),

t.stringLiteral(symbol),
value,
],
value
]
);

@@ -99,3 +99,3 @@ }

return (
target.object.name === 'module' && target.property.name === 'exports'
target.object.name === "module" && target.property.name === "exports"
);

@@ -118,3 +118,3 @@ }

const collectCommonJSImports = {
VariableDeclarator(path, {excludeImportsFromModules, imports}) {
VariableDeclarator(path, { excludeImportsFromModules, imports }) {
// If the `require` is wrapped in some way, we just ignore it, since

@@ -143,4 +143,4 @@ // we cannot determine which symbols are being required without knowing

if (id.type === 'Identifier') {
if (id.name.startsWith('_')) {
if (id.type === "Identifier") {
if (id.name.startsWith("_")) {
// Assume that imports with an underscore-prefixed name have been

@@ -151,3 +151,3 @@ // automatically generated by Babel. Do not make them mockable.

const symbol = '<CJS>';
const symbol = "<CJS>";
imports.push({

@@ -157,5 +157,5 @@ alias: id.name,

source,
value: id,
value: id
});
} else if (id.type === 'ObjectPattern') {
} else if (id.type === "ObjectPattern") {
// `var { aSymbol: localName } = require("a-module")`

@@ -173,7 +173,7 @@ for (let property of id.properties) {

symbol,
value,
value
});
}
}
},
}
};

@@ -210,14 +210,14 @@

t.importSpecifier(
t.identifier('ImportMap'),
t.identifier('ImportMap'),
),
t.identifier("ImportMap"),
t.identifier("ImportMap")
)
],
t.stringLiteral(helperImportPath),
t.stringLiteral(helperImportPath)
);
const $importsDecl = t.variableDeclaration('const', [
const $importsDecl = t.variableDeclaration("const", [
t.variableDeclarator(
t.identifier('$imports'),
t.newExpression(t.identifier('ImportMap'), []),
),
t.identifier("$imports"),
t.newExpression(t.identifier("ImportMap"), [])
)
]);

@@ -227,8 +227,8 @@

t.exportSpecifier(
t.identifier('$imports'),
t.identifier('$imports'),
),
t.identifier("$imports"),
t.identifier("$imports")
)
]);
const body = path.get('body');
const body = path.get("body");

@@ -250,17 +250,17 @@ // Insert `$imports` declaration below last import.

t.memberExpression(
t.identifier('module'),
t.identifier('exports'),
t.identifier("module"),
t.identifier("exports")
),
t.identifier('$imports'),
t.identifier("$imports")
);
const cjsExport = t.expressionStatement(
t.assignmentExpression(
'=',
"=",
moduleExportsExpr,
t.identifier('$imports'),
),
t.identifier("$imports")
)
);
body[body.length - 1].insertAfter(cjsExport);
}
},
}
},

@@ -275,3 +275,3 @@

// Skip assignments that are not at the top level.
if (path.parentPath.parent.type !== 'Program') {
if (path.parentPath.parent.type !== "Program") {
return;

@@ -291,3 +291,6 @@ }

// the right side of the assignment other than the `require` call.
if (!t.isIdentifier(path.node.left) || !t.isCallExpression(path.node.right)) {
if (
!t.isIdentifier(path.node.left) ||
!t.isCallExpression(path.node.right)
) {
return;

@@ -318,3 +321,3 @@ }

path.insertAfter(
createAddImportCall(ident.name, source, '<CJS>', ident)
createAddImportCall(ident.name, source, "<CJS>", ident)
);

@@ -330,3 +333,3 @@ },

// Ignore non-top level CommonJS imports.
if (path.parent.type !== 'Program') {
if (path.parent.type !== "Program") {
return;

@@ -337,10 +340,10 @@ }

const imports = [];
const {excludeImportsFromModules} = state.opts;
const { excludeImportsFromModules } = state.opts;
path.traverse(collectCommonJSImports, {
excludeImportsFromModules,
imports,
imports
});
// Register all found imports.
imports.forEach(({alias, source, symbol, value}) => {
imports.forEach(({ alias, source, symbol, value }) => {
state.importIdentifiers.add(value);

@@ -358,3 +361,3 @@ path.insertAfter(createAddImportCall(alias, source, symbol, value));

path.node.specifiers.forEach(spec => {
if (spec.local.name === '$imports') {
if (spec.local.name === "$imports") {
// Abort processing the file if it declares an import called

@@ -368,11 +371,11 @@ // `$imports`.

switch (spec.type) {
case 'ImportDefaultSpecifier':
case "ImportDefaultSpecifier":
// import Foo from './foo'
imported = 'default';
imported = "default";
break;
case 'ImportNamespaceSpecifier':
case "ImportNamespaceSpecifier":
// import * as foo from './foo'
imported = '*';
imported = "*";
break;
case 'ImportSpecifier':
case "ImportSpecifier":
// import { foo } from './foo'

@@ -382,3 +385,3 @@ imported = spec.imported.name;

default:
throw new Error('Unknown import specifier type: ' + spec.type);
throw new Error("Unknown import specifier type: " + spec.type);
}

@@ -393,3 +396,3 @@

path.insertAfter(
createAddImportCall(spec.local.name, source, imported, spec.local),
createAddImportCall(spec.local.name, source, imported, spec.local)
);

@@ -421,4 +424,4 @@ });

t.isIdentifier(callee.object) &&
callee.object.name === '$imports' &&
callee.property.name === '$add'
callee.object.name === "$imports" &&
callee.property.name === "$add"
) {

@@ -431,3 +434,3 @@ return;

// Bailing out here means that such code will at least compile.
if (child.parent.type === 'ExportSpecifier') {
if (child.parent.type === "ExportSpecifier") {
return;

@@ -438,11 +441,11 @@ }

if (
child.parent.type === 'JSXOpeningElement' ||
child.parent.type === 'JSXClosingElement' ||
child.parent.type === 'JSXMemberExpression'
child.parent.type === "JSXOpeningElement" ||
child.parent.type === "JSXClosingElement" ||
child.parent.type === "JSXMemberExpression"
) {
child.replaceWith(
t.jsxMemberExpression(
t.jsxIdentifier('$imports'),
t.jsxIdentifier(child.node.name),
),
t.jsxIdentifier("$imports"),
t.jsxIdentifier(child.node.name)
)
);

@@ -452,10 +455,10 @@ } else {

t.memberExpression(
t.identifier('$imports'),
t.identifier(child.node.name),
),
t.identifier("$imports"),
t.identifier(child.node.name)
)
);
}
},
},
}
}
};
};

@@ -77,5 +77,13 @@ "use strict";

*
* @param {Object} imports -
* Map of file path (as used in the module that imports the file) to
* replacement content for that module.
* @param {Object|Function} imports -
* An object whose keys are file paths (as used by the module being
* tested, *not* the test module) and values are objects mapping export
* names to mock values. As a convenience, the value can also be a
* function in which case it is treated as a mock for the module's
* default export.
*
* Alternatively this can be a function which accepts
* (source, symbol, value) arguments and returns either a mock for
* that import or `null`/`undefined` to avoid mocking that import.
* This second form is useful for mocking many imports at once.
*/

@@ -87,2 +95,19 @@ ;

if (typeof imports === "function") {
var mocks = {};
Object.keys(this.$meta).forEach(function (alias) {
var _this$$meta$alias = _this.$meta[alias],
source = _this$$meta$alias[0],
symbol = _this$$meta$alias[1],
value = _this$$meta$alias[2];
var mock = imports(source, symbol, value);
if (mock != null) {
mocks[source] = mocks[source] || {};
mocks[source][symbol] = mock;
}
});
this.$mock(mocks);
}
Object.keys(imports).forEach(function (source) {

@@ -92,3 +117,3 @@ var sourceImports = imports[source];

if (typeof esImports === 'function') {
if (typeof esImports === "function") {
esImports = {

@@ -101,6 +126,6 @@ "default": esImports

var namespaceAliases = Object.keys(_this.$meta).filter(function (alias) {
var _this$$meta$alias = _this.$meta[alias],
source_ = _this$$meta$alias[0],
symbol_ = _this$$meta$alias[1];
return source_ === source && symbol_ === '*';
var _this$$meta$alias2 = _this.$meta[alias],
source_ = _this$$meta$alias2[0],
symbol_ = _this$$meta$alias2[1];
return source_ === source && symbol_ === "*";
});

@@ -112,6 +137,6 @@ namespaceAliases.forEach(function (alias) {

var cjsAliases = Object.keys(_this.$meta).filter(function (alias) {
var _this$$meta$alias2 = _this.$meta[alias],
source_ = _this$$meta$alias2[0],
symbol_ = _this$$meta$alias2[1];
return source_ === source && symbol_ === '<CJS>';
var _this$$meta$alias3 = _this.$meta[alias],
source_ = _this$$meta$alias3[0],
symbol_ = _this$$meta$alias3[1];
return source_ === source && symbol_ === "<CJS>";
});

@@ -125,5 +150,5 @@ cjsAliases.forEach(function (alias) {

var aliases = Object.keys(_this.$meta).filter(function (alias) {
var _this$$meta$alias3 = _this.$meta[alias],
source_ = _this$$meta$alias3[0],
symbol_ = _this$$meta$alias3[1];
var _this$$meta$alias4 = _this.$meta[alias],
source_ = _this$$meta$alias4[0],
symbol_ = _this$$meta$alias4[1];
return source_ === source && symbol_ === symbol;

@@ -130,0 +155,0 @@ });

{
"name": "babel-plugin-mockable-imports",
"version": "1.4.0",
"version": "1.5.0",
"description": "Babel plugin for mocking ES imports",
"main": "index.js",
"scripts": {
"checkformatting": "prettier --check **/*.js",
"checkformatting": "prettier --check *.js test/*.js",
"build": "babel helpers.js --out-dir lib",
"lint": "eslint --ignore-pattern lib/* .",
"format": "prettier --write **/*.js",
"format": "prettier --write *.js test/*.js",
"test": "mocha && npm run lint && npm run checkformatting",

@@ -12,0 +12,0 @@ "prepublishOnly": "npm run build"

@@ -5,11 +5,21 @@ # babel-plugin-mockable-imports

A Babel plugin that modifies modules to enable mocking of their dependencies. The plugin was written with the following goals:
A Babel plugin that transforms JavaScript modules (CommonJS or ES2015 style)
to enable mocking of their dependencies in tests.
- Provide a simple interface for mocking ES and CommonJS (`require`) module imports
- Work when running tests in Node with any test runner or in the browser when
using any bundler (Browserify, Webpack etc.) or no bunder at all
- Minimize the amount of extra code added to modules, since extra code
impacts test execution time
- Catch common mistakes and warn about them
See the Usage section below for information on getting started, and the FAQ at
the end of the README for a comparison to alternative solutions.
## Features
- Provides a simple interface for mocking imports in tests
- Works with CommonJS (Node style) and native (ES2015) JavaScript modules
- Can be used with any test runner, any bundler, and whether tests are being run
under Node or in the browser
- Transforms code in a straightforward way that is easy to debug if necessary
- Minimizes the amount of extra code added to modules, to reduce the impact
on test execution time
- Detects incorrect usage (eg. mocking a module or import that is not used)
and causes a test failure if this happens
- Can be used with both JavaScript and TypeScript
## Usage

@@ -108,2 +118,5 @@

See the [example project](examples/javascript) for a complete runnable project
using Mocha as a test runner.
### Mocking default exports

@@ -143,2 +156,42 @@

### Mocking all imports that match a pattern
In some tests you may want to mock many dependencies in the same way, or ensure
that all imports meeting certain criteria in a module are mocked consistently.
You can pass a function to `$imports.$mock` which will be called with the
source, symbol name and original value of each import. The result of the
function will be used as the mock for that import if it is not `null`.
For example, to mock all functions imported by a module, you can use:
```js
$imports.$mock((source, symbol, value) => {
if (typeof value === 'function') {
// Mock functions using Sinon.
return sinon.stub();
} else {
// Skip mocking objects, constants etc.
return null;
}
});
```
To ensure that a test mocks every imported function, you can use:
```js
// Throw an error if any unmocked function is called.
$imports.$mock((source, symbol, value) => {
if (typeof value === 'function') {
return () => throw new Error('Function not mocked');
}
return null;
});
// Setup mocks for expected imports.
$imports.$mock({
'./util': { doSomething: fakeDoSomething },
});
```
### Limiting mocking to specific files

@@ -189,2 +242,12 @@

## Usage with TypeScript
It is possible to use this plugin with TypeScript. In order to do that you need
to transform your TypeScript code using Babel when running tests, and also
use a helper function to get access to the `$imports` object for a module.
Since this object is not present in the original source, the TypeScript compiler
is not aware of its existence.
See the [typescript example project](examples/typescript) for a runnable example.
## How it works

@@ -191,0 +254,0 @@

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