npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

esm-unit

v1.0.0-beta.4

Published

Unit testing native ES Modules in web browsers

Downloads

17

Readme

ESM Unit

NPM Version

Lightning-fast unit testing for native ECMAScript modules.

Features

  • Run unit tests in real web browsers as pure ECMAScript modules, without any bundling or transpilation steps.
  • Easy debugging using any standard JavaScript debugger.
  • Each test file is run in an isolated iframe, with a clean window and DOM document to work with. Test files can't normally interfere with each others' mocks.
  • Quickly isolate and rerun any failing test by itself, by clicking "Isolate" in the debug browser window.

Installation

npm add --save-dev esm-unit

Usage:

Requirements

ESM Unit requires ECMAScript module code capable of running in current web browsers without any transpilation step. If your code uses nonstandard tools like JSX, or uses import to load anything other than native ES modules from local relative file paths, it will need to be converted before running this.

One option is to use a dev server to translate JSX to ESM code on the fly, and replacing nonstandard imports with their original API versions (such as require() for AMD packages). You can provide a custom dev server using the --http-server command line option.

Node 18 LTS or later is required to use ESM Unit. Node 20 LTS is required for development.

A test file

In tests/suite.test.js:

import {
  describe,
  test,
  assert,
  before,
  beforeEach,
  after,
  afterEach
} from "esm-unit/test-api.js";
import { wait } from "./util/async.js";

describe("my tests", () => {
  // Describes a test suite

  before(() => {
    // Optional setup run before the first test in this suite
  });
  after(() => {
    // Optional teardown run after the last test in this suite
  });
  beforeEach(() => {
    // Optional setup run before every test in this suite
  });
  afterEach(() => {
    // Optional teardown run before every test in this suite
  });

  describe("these should pass", () => {
    test("synchronous test", () => {
        assert.equal(1, 1);
    });
    test("asynchronous test", async () => {
      await wait(10);
      assert.equal(1, 1);
    });
  });
  describe("testing failures", () => {
    test("synchronous failure", () => {
      assert.fail("This should fail");
    });
    test("asynchronous failure", async () => {
      await wait(10);
      assert.equal(1, "1", "Equality checks are strict");
    });
  });
});

Any test that returns a promise is treated as an asynchronous test.

Run a single test

In headless Chrome from the command line:

First, ensure chromedriver is installed and updated. You can do this by running npm install -g chromedriver, and ensure the path returned by npm bin -g is added to your PATH variable in .bash_profile for bash or .zshrc for zsh. Then from the root of your project:

esm-unit --suite=tests/suite.test.js --headless

To specify any other browser, provide the browser name via --browser:

esm-unit --suite=tests/suite.test.js --browser=firefox

To test multiple browsers in parallel and combine their coverage stats into a single report, this can be set multiple times:

esm-unit --suite=tests/suite.test.js --browser=chrome --browser=safari --browser=firefox

Note: Browser logs are only printed from Chrome, and headless mode is only supported in Chrome and Firefox. Using other browsers may require installing their driver executable, such as geckodriver.

Debug a single test

To open the test in debug mode in your default web browser:

esm-unit --debug --suite=tests/suite.test.js

If a test fails, you can click "Isolate" in the browser to rerun and debug only that test.

Save a configuration for running and debugging multiple tests:

Save a config file in esm-unit.json in the root of your project:

{
    "importMap": {
      "imports": {
        "esm-unit/": "/node_modules/esm-unit/"
      }
    },
    "testFiles": [
        "src/**/*.test.js"
    ]
}

Or in esm-unit.js to generate it with code:

const { getLocale } = require("./utils.js");
module.exports = {
    "importMap": {
      "imports": {
        "esm-unit/": "/node_modules/esm-unit/",
      },
    },
    "testFiles": [
        "src/**/*.test.js",
    ],
    "appendParam": filePath => `?locale=${getLocale(filePath)}`,
};

testFiles can be file paths or glob patterns. If using glob patterns, you can also provide "excludeTestFiles" with an array of file paths or patterns to avoid.

Then run esm-unit (with or without --debug):

esm-unit --headless

Automatically run tests every time a file changes

Add --watch to scan your project's directory for any changes and automatically re-run your tests. This can be configured by setting the root directory to "watchDirectory" in your esm-unit.json file:

{
    "watchDirectory": "src",
    "testFiles": [
        "src/**/*.test.js"
    ]
}

The --watch and --debug options can't be used at the same time. With --debug on, refresh the web browser to re-run tests.

All config options

These options are also available in your esm-unit.json or esm-unit.js file:

  • module (boolean): Whether to load test files as native ECMAScript modules. Defaults to true.
  • testFiles (array of glob patterns): A list of paths to scan for test files.
  • watchDirectory (path): Root directory to watch for changes when running with --watch
  • includeScripts (array of paths): a list of JavaScript files to load as ordinary scripts before each test
  • includeModules (array of paths): a list of JavaScript files to load as native ECMAScript modules before each test
  • appendQuery (string|function): An optional query parameter, like ?env=test, to append to each JavaScript file path. Useful with custom dev servers like es6-module-server. If providing a function, it will take the test file path as an argument.
  • coverage (object): Code coverage configuration. See below for details.
  • failOnErrorLogs (boolean) Tests will be marked as failed if any errors appear in the browser console, such as uncaught tracebacks and console.error messages. (Chrome and MS Edge only).
  • importMap (object): An import map following the WHATWG specification. The object set here will be included in a <script type="importmap"> in test suites. When set, unit tests will only run in web browsers with native support for import maps.

Code Coverage

esm-unit supports integrated per-file code coverage enforcement.

Code coverage reports are enabled by default when running without --debug, but can be disabled with --no-coverage.

To enable coverage threshold enforcement, add a coverage configuration block to your esm-unit.json config file:

"coverage": [
  {
    "includeFiles": [
      "src/**/*.js"
    ],
    "excludeFiles": [
      "src/**/*.test.js",
    ],
    "threshold": {
      "branches": 80,
      "functions": 80,
      "lines": 80,
      "statements": 80
    }
  },
]

Add additional objects to the coverage array to override settings for individual files. Each file will use the thresholds of the last matching object.

Assertions

Any assertion library is supported, as long as it can be loaded in web browsers as an ECMAScript module.

ESM Unit includes the following built-in assertions, based on the classic JSUnit API. [description] is an optional but recommended string message to be printed on failure.

| Function | Asserts that... | |----------|-----------------| | assert(value, [description]) | value is truthy | | assert.true(value, [description]) | value is strictly true | | assert.false(value, [description]) | value is strictly false | | assert.equal(expected, actual, [description]) | actual is strictly equal to expected | | assert.notEqual(expected, actual, [description]) | actual is not strictly equal to expected | | assert.stringMatches(regExp, actual, [description]) | actual matches the regular expression regExp | | assert.deepEqual(expected, actual, [description]) | actual deeply matches the object provided in expected | | assert.instanceOf(child, constructor, [description]) | child is an instance of constructor | | assert.throws(func, [errorText], [description]) | func throws an error. If errorText is provided, that text must be contained within the error description. | | await assert.rejects(func or promise, [errorText], [description]) | promise rejects, or func returns a promise that rejects. If errorText is provided, that text must be contained within the error message. | | assert.fail([description]) | No assertion; always fails |

import {describe, test, assert} from "../node_modules/esm-unit/test-api.js";

describe("math", () => {
  test("addition", () => {
    assert.equal(4, 2 + 2, "Math should work");
  });
});

Stubs and Mocks

Sinon is recommended for stubbing and mocking, and performing assertions against those mocks.

npm install --save-dev sinon

As it's a native ECMAScript module, you can add it to your import map in esm-unit.json for easier imports:

    "importMap": {
      "imports": {
        "esm-unit/": "/node_modules/esm-unit/",
        "sinon": "/node_modules/sinon/pkg/sinon-esm.js"
      }
    },
    "testFiles": [
        "src/**/*.test.js"
    ]

Sinon's own assertion API can be used to validate mocks and spies.

import {describe, test, beforeEach} from "esm-unit/test-api.js";
import sinon from "sinon";

describe("sinon's spies", () => {
  const sinonSandbox = sinon.createSandbox();
  afterEach(() => {
    sinonSandbox.restore();
  });

  test("console.log()", () => {
    sinonSandbox.spy(console, "log");
    console.log("Hello, World");
    sinon.assert.calledWith(console.log, "Hello, World");
  });
});

Contributing

Feel free to open an issue or a pull request!

Make sure to read our code of conduct.

We actively welcome pull requests. Learn how to contribute.