A babel plugin adding the ability to rewire module dependencies. This enables to mock modules for testing purposes.
npm install babel-plugin-rewire-ts__GetDependency__, __Rewire__, and __ResetDependency__.
__get__ and __set__ are exported as well.
__set__ will return a revert function like rewire.js.
__RewireAPI__ is exported as named export as well as a property of the default export. This object itself contains all the functions mentioned above as fields. This enables one to rewire members of the imported module itself without explicitly importing the module (see Handling of default exports below).
javascript
import ChildComponent from "child-component-module";
export default class MyFancyWrapperComponent extends React.Component {
render() {
return (
);
}
}
`
$3
`javascript
import ComponentToTest from 'my-fancy-wrapper-component-module';
ComponentToTest.__Rewire__('ChildComponent', React.createClass({
render: function() { return ; }
}));
....
ComponentToTest.__ResetDependency__('ChildComponent');
`
Node/browserify require() and top-level var support
Variables declared and initialised at the top level, such as those from require() calls, can be rewired
$3
`javascript
var Path = require("path");
var env = "production";
module.exports = function(name) {
return Path.normalise(name);
};
`
$3
`javascript
var Normaliser = require('Normaliser');
Normaliser.__Rewire__('Path', {
normalise: (name) => name;
});
Normaliser.__Rewire__('env', 'testing');
....
Normaliser.__ResetDependency__('Path');
`
Named and top level function rewiring
Besides top level variables also top level functions defined in the imported module can be rewired.
When exported functions of a module depend on each other it can be convenient to test them independently.
Hence, babel-plugin-rewire allows you to rewire the internal dependencies to exported named functions as shown in the example below.
Be aware, that rewiring a named export does not influence imports of that same export in other modules!
$3
Asuming you have a module TodoOperations.js that internaly uses an asynchronous api to fetch some information
`js
function fetchToDos() {
...
return new Promise(...);
}
export function filterToDos( filterString ) {
return fetchToDos().then( function( todos ) {
// Highly fashioned filter function code ...
return filteredToDos;
});
}
export function filterAndSortToDos( filterString, sortOrder ) {
return fetchToDos( filterString ).then( function( filteredToDos ) {
// Higly fashioned sort function
return filteredAndSortedToDos;
});
}
`
$3
In your test you can mock your API-calls to simply return static dummy data like this
`js
import { filterToDos, filterAndSortToDos, __RewireAPI__ as ToDosRewireAPI } from "TodoOperations.js";
describe("api call mocking", function() {
it("should use the mocked api function", function(done) {
ToDosRewireAPI.__Rewire__("fetchToDos", function() {
return Promise.resolve(["Test more", "Refine your tests", "Tests first rocks"]);
});
filterToDos("Test")
.then(function(filteredTodos) {
//check results
done();
})
.catch(e => fail());
ToDosRewireAPI.__ResetDependency__("fetchToDos");
});
it("should use the mocked filter function", function(done) {
ToDosRewireAPI.__Rewire__("filterToDos", function() {
return Promise.resolve(["02 Test more", "01 Test even more"]);
});
filterAndSortToDos("Test", "asc")
.then(function(filteredAndSortedTodos) {
//check results
done();
})
.catch(e => fail());
ToDosRewireAPI.__ResetDependency__("filterToDos");
});
});
`
Handling of default exports
If a non primitive default export is present in the imported module, it is enriched with the API-Functions and the API-Object.
If no default export is present, the API-Object named __RewireAPI__ becomes the default export of the module.
This object basically supports all the rewire API-Functions as described in the introduction above and allows one to rewire the module without explicitly importing the module itself.
$3
Asuming your imported module does not have a default export specified like in this simple example
`js
function message() {
return "Hello world";
}
export function foo() {
return message();
}
`
$3
In your test you would use the default exported API-Object to rewire the function message of the imported module like this
`js
import FooModule from "foo.js";
import { foo, __RewireAPI__ as FooModuleRewireAPI } from "foo.js";
describe("module default export test", function() {
it("should demonstrate the default exported rewire api", function() {
expect(foo()).to.equal("Hello world");
FooModule.__Rewire__("message", function() {
return "my message";
});
expect(foo()).to.equal("my message");
FooModule.__ResetDependency__("message");
});
it("should demonstrate the rewire apis named export", function() {
expect(foo()).to.equal("Hello world");
FooModuleRewireAPI.__Rewire__("message", function() {
return "my message";
});
expect(foo()).to.equal("my message");
FooModuleRewireAPI.__ResetDependency__("message");
});
});
`
Handling of async functions
Rewiring of async functions works as one would expect using the same API as for other rewires for both default exports and named exports.
$3
Assuming your imported module consists of the following.
`js
// api.js
export default async function asyncApiDefault() {
return await asyncApi();
}
export async function asyncApi() {
return await api();
}
function api() {
// Some async API call
return Promise.resolve("API Response");
}
`
$3
In your test you would use the default exported API-Object to rewire the function asyncApiDefault and asyncApi of the imported module like this.
`js
import { default as asyncApiDefault, asyncApi, __RewireAPI__ as AsyncApiRewireAPI } from "api.js";
describe("async function export test", function() {
it("should be able to rewire default async function", function() {
return asyncApiDefault().then(response => {
expect(response).to.equal("API Response");
AsyncApiRewireAPI.__set__("asyncApi", function() {
return Promise.resolve("Mock API Response");
});
return asyncApiDefault().then(response => {
expect(response).to.equal("Mock API Response");
AsyncApiRewireAPI.__ResetDependency__("asyncApi");
});
});
});
it("should be able to rewire non default async function", function() {
return asyncApi().then(response => {
expect(response).to.equal("API Response");
AsyncApiRewireAPI.__set__("api", function() {
return Promise.resolve("Mock API Response");
});
return asyncApi().then(response => {
expect(response).to.equal("Mock API Response");
AsyncApiRewireAPI.__ResetDependency__("api");
});
});
});
});
`
Resetting all
When "babel-plugin-rewire" is used the global method __rewire_reset_all__ is added.
Each time this method is called all rewired dependencies across all modules are reset.
$3
Assuming you have two imported modules:
Module1:
`js
var value = "Module1-Original";
export default function getModule2Identifier() {
return value;
}
`
Module2:
`
var value = 'Module2-Original';
export default function getModule2Identifier() {
return value;
}
`
$3
In your test by calling __rewire_reset_all__ all dependencies are reset and you can ensure that no rewired data will harm subsequent tests.
`js
import getModule1Identifier from "./src/Module1.js";
import getModule2Identifier from "./src/Module2.js";
import expect from "expect.js";
describe("__rewire_reset_all__", function() {
it("should allow to reset all rewired dependencies", function() {
expect(getModule1Identifier()).to.be("Module1-Original");
expect(getModule2Identifier()).to.be("Module2-Original");
getModule1Identifier.__set__("value", "module1-rewired");
getModule2Identifier.__set__("value", "module2-rewired");
expect(getModule1Identifier()).to.be("module1-rewired");
expect(getModule2Identifier()).to.be("module2-rewired");
__rewire_reset_all__();
expect(getModule1Identifier()).to.be("Module1-Original");
expect(getModule2Identifier()).to.be("Module2-Original");
});
});
`
Installation
`
$ npm install babel-core babel-plugin-rewire
`
Usage
To use the plugin identify it by its long name "babel-plugin-rewire-ts" or by its abbreviation
"rewire-ts".
If you are using @babel/plugin-tranform-typescript and encounter these warnings:
`
The exported identifier "_get__" is not declared in Babel's scope tracker
as a JavaScript value binding, and "@babel/plugin-transform-typescript"
never encountered it as a TypeScript type declaration.
It will be treated as a JavaScript value.
...
`
We recommend you switch to @babel/preset-typescript, since it type-checks
your code while the plugin does not. Or use @babel/preset-env along with
the plugin to resolve those warnings. We already fixed the issue on our side
and reason behind those warning is unclear and they are harmless.
$3
abbreviated:
`
$ babel --plugins rewire ..
`
full plugin name:
`
$ babel --plugins babel-plugin-rewire ..
`
$3
You can also specify plugins via the babelrc file:
`json
{
"plugins": ["rewire"]
}
`
Whether you're using the command line, JS API, or require hook, this file is honored by babel.
$3
abbreviated:
`javascript
require("babel-core").transform("code", { plugins: ["rewire"] });
`
full plugin name:
`javascript
require("babel-core").transform("code", { plugins: ["babel-plugin-rewire"] });
`
$3
`javascript
require("babel-register")({
plugins: ["babel-plugin-rewire"]
});
`
$3
abbreviated:
`javascript
{test: /src\/js\/.+\.js$/, loader: 'babel-loader?plugins=rewire' }
`
full plugin name:
`javascript
{test: /src\/js\/.+\.js$/, loader: 'babel-loader?plugins=babel-plugin-rewire' }
`
$3
full plugin name:
`javascript
var appBundler = browserify({
entries: [test.src] // Only need initial file, browserify finds the rest
}).transform(
babelify.configure({
plugins: [require("babel-plugin-rewire")]
})
);
`
Combining with other plugins/tools
$3
To integrate babel-plugin-rewire with istanbul, it is recommended to use babel-plugin-istanbul.
This babel plugin instruments your code with Istanbul coverage.
It has been reported that the order of plugins are important. Therefore prefer the following order:
`json
{
"plugins": ["istanbul", "rewire"]
}
`
For a project integrating karma, babel, babel-plugin-rewire and istanbul please see karma-rewire-istanbul-example
$3
There are some things to consider when using babel-plugin-rewire together with isparta. Since isparta runs Babel itself it's important to remember to add the same configuration options to it as you would do with Babel. If you forget this you will in some cases see unexpected errors.
If you use _.babelrc_ it's advised that you run your tests with a specific ENV, for example "test", and add the following to your _.babelrc_.
Furthermore in case you use isparta only add the plugin once in the isparta loader and not in the babel loader as well.
`json
"env": {
"test": {
"plugins": ["rewire"]
}
}
`
If you are using isparta together with Webpack you could also do something like this.
`javascript
webpack: {
isparta: {
embedSource: true,
noAutoWrap: true,
babel: {
plugins: 'rewire'
}
},
preLoaders: [
...
{
test: /\.js$/,
include: path.resolve('src/'), //only source under test
loader: 'isparta'
},
]
...
}
``