Skip to content

kevinbarabash/babel-plugin-export-everything

Repository files navigation

babel-plugin-export-everything

The goal of this babel plugin is to make it easier mock things when writing unit tests without having to change how you'd normally code. In particular it allows you to mock top-level functions and variables from any source module even if it hasn't been exported.

Writing tests

foo.js

const msg = "foo";

export const foo = () => msg;

foobar.js

import {foo} from "./foo.js";

const msg = "bar";
const bar = () => msg;

export const foobar = () => {
    return foo() + bar();
};

example.test.js

const mockValue = (obj, prop, value) => {
    jest.spyOn(obj, prop, "get").mockReturnValue(value);
};

test("mocking private variable", () => {
    mockValue(FooBar, "msg", "baz");

    expect(FooBar.foobar()).toEqual("foobaz");
});

test("mocking private function", () => {
    jest.spyOn(FooBar, "bar").mockReturnValue("baz")

    expect(FooBar.foobar()).toEqual("foobaz");
});

test("mocking private variable in dependency", () => {
    mockValue(Foo, "msg", "qux");

    expect(FooBar.foobar()).toEqual("quxbar");
});

test("mocking function from dependency", () => {
    jest.spyOn(Foo, "foo").mockReturnValue("qux")

    expect(FooBar.foobar()).toEqual("quxbar");
});

See the example.test.js for the full test suite.

How it works

All top-level declarations in all non-test modules to be named exports using commonjs' exports object and updates all references to each variable accordingly.

input.js

const msg = "foo";
const foo = () => msg;
export const foobar = () => `${foo()}bar`;

output.js

const msg = "foo",
Object.defineProperty(exports, "msg", {
    enumerable: true,
    configurable: true,
    get: () => msg,
});

let foo = () => exports.msg;
Object.defineProperty(exports, "foo", {
    enumerable: true,
    configurable: true,
    get: () => foo,
    set: (newValue) => foo = newValue,
});

let foobar = () => `${exports.foo()}bar`;
Object.defineProperty(exports, "foobar", {
    enumerable: true,
    configurable: true,
    get: () => foobar,
    set: (newValue) => foobar = newValue,
});

Object.defineProperty(exports, "__esModule", {
    value: true,
});

The reason for setting exports.__esModule = true at the end is that other modules importing this module may end up using _interopRequireDefault in the code generated by babel. This helper has the following defintion:

function _interopRequireDefault(obj) {
    return obj && obj.__esModule 
        ? obj 
        : { default: obj };
}

const Foo = _interopRequireDefault(require("./output.js"));

In order have consistent operation regardless of whether the helper is being used or not, we need to set __esModule = true so that we always returns whatever is returned by calls to require.

Warning

In order for coverage to work, please set coverageProvider: "v8" in your jest configuration. Without this, some tests will fail because of how the traditional code coverage modifies your code to instrument it.

TODOs

  • require-ing dependencies
  • re-exporting things
  • renaming things in exports, e.g. export { foo as bar, baz as default }
  • createReactClass classes

Developing

jest caches compiled files so often times you'll need to clear the cache before your changes will show up. This can be done by running:

yarn test --clearCache

About

Exports all top-level declarations from a file. This can be used to mock private variables and functions.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published