@haraldki/unittesterjs - v2.0.0
    Preparing search index...

    @haraldki/unittesterjs - v2.0.0

    UnitTesterJS — Lightweight Unit Test Runner for JavaScript

    Table of Contents

    This can be installed directly from codeberg:

    npm i -D git+https://codeberg.org/harald/unittesterjs.git
    

    For details, see the api documentation. Here is a quick overview.

    This package provides the class UnitTester to run test functions while keeping track of failures, logging them and to provide a summary. It does not provide "assert" or "expect" functions to check conditions. There are packages provide this, for example chai, which has no dependencies.

    Lets go through a minimal example fragment:

    class TestStuff {
    testDogBarks(): void {
    }
    testCatIsNotDog(): void {
    }
    }
    const ut = new UnitTester(...);
    await ut.run(new TestStuff());
    ut.summarize();
    • Create an instance of UnitTester, say ut.
    • Create one or more classes which contain methods with names matching test.... Or configure the pattern when creating ut.
    • Call ut.run(...) for the instances of your test classes. It runs all test... methods and keeps track of sucessful, ignored, failing and crashing ones. Depending on the logger provided when creating ut, more or less details are logged during run().
    • Call ut.summarize() to get a one-liner with counts about successful, ignored, failed and crashed methods.

    Follow the general strategy above to write tests, but wrap the main code, into

    document.addEventListener("DOMContentLoaded", async () => {
    ...
    }

    You may use the HtmlTestLogger instead of the DefaultTestLogger. Then load the file into a browser with a trivial HTML file, the gist of which is:

      <script type="module" src=".../path/from/html/to/MyTest.js"></script>
    

    If the browser complains about imports like

    import { assert, AssertionError } from "chai";
    

    read about importmap on mdn;

    See the examples in the source code:

    Your development environment likely provides means already to run a test HTTP server and serve the HTML file loading your test file. If not, check node or python documentation for one liners.

    The goal was to provide as little magic as possible, in particular where boring old normal programming provides already all that is needed, like for parametrized tests or magically finding test files. Read on.

    Test classes may have the methods beforeEach, afterEach and afterAll with the obvious meaning. There is no beforeAll, use the constructor instead.

    A test class may extend from a base class. Before/after and test methods of the base class are used if not overriden, just normal business.

    Let the test class constructor have parameters, create instances with different parameters and pass them ut.run(), like:

    const ut = new UnitTester(...);
    for(const animal of ["dog", "cat", "chicken"]) {
    ut.run(new AnimalTest(animal));
    }

    For better identification of test instances in the logs, set the test class field paramId to some recognizable string derived from the parameters.

    The difference is defined when creating the UnitTester. The "assert" or "expect" conditions you use likely throw a specific subclass of Error. These are failed tests. Other Error instances count as crashed. Making the difference is a bit gold plating, and not extremely important. If a test method throws, things are broken and need to be fixed. Test functions which return anything but undefined are crashed too.

    These are defined by a regular expression passed to the UnitTester constructor which defaults to /^test/. UnitTester.run() has a parameter to override it per test instance. A matching function with more than zero parameters is logged as a mistake and not run as a test.

    A module (basically a JavaScript or TypeScript file) with a bunch of test classes can be passed to UnitTester.runAll(). All constructor bearing functions, typically class objects, which match /^Test/ by default, are called with new and no parameters to create a test class instance. Each is handed to UnitTester.run() as described above.

    For better logging and statistics, it is nice to have something like

    export const name = import.meta.url.split('/').splice(-2).join('/');
    

    in a module, but this is optional.

    FIXME: Running test classes in a module with constructor parameters is not implemented.

    There is nothing provided to collect JavaScript files (modules) with tests. Write a TypeScript/JavaScript file as outlined above and run it. See the main function in our own test as an example.

    Progress and failures are logged through a logger provided when constructing the UnitTester. The type is TestLogger and may be completely replaced. The default is a DefaultTestLogger which itself can be configured, see TestLoggerOptions.

    To skip a test instance, you could simply comment out the code which calls UnitTester.run(), but this aggravates the compiler because of unused code and you may eventually forget to uncomment the code again. Therefore we have UnitTester.runX(). Simply change from .run() to .runX() to skip the test instance, but have a reminder in the log output about it. The same holds for .runAllX() as a drop-in for .runAll() to skip whole modules as you may want during debugging.

    To ignore a test function depending on some condition, use the following at the start of its body:

    if (condition) {
    throw new TestIgnored("this needs haxwurbel to be implemented first");
    }

    It will be logged as a reminder that it needs work, except if the logger is configured to not do so. If there is no condition and you only want to get rid of the test for a while, use

    TestIgnored.throw("some explanation")
    

    The effect is the same as with throw new, except that the compiler will not bother you about unreachable code.

    Because async test methods are supported, UnitTester.run() ends up being async too, sorry. So your compiler or linter will likely tell you to deal with the Promise returned, whether you have any async test at all or not.

    To verify that a test can finish within some reasonably limited time, use UnitTester.failAfterMs(). It sets up a timout to fail the promise representing the test function after the given time. The test function counts as crashed (see above) then. Example:

    class AnimalTest {
    constructor(private readonly ut: UnitTester) {}
    testDogBarks(): void {
    ut.failAfter(500);
    await dog.barks();
    }
    }

    This assumes that the UnitTester passed to the constructor is the one executing the test.

    There are tons of unit test frameworks for JavasScript. Their task can be partitioned into the following aspects:

    1. Coding boolean expressions in a nice and readable way, like assertThat("bobo").isA(Cat)
    2. Coding test functions like it('verifies the dog can bark', ...) or just @Test barkingDogTest() {...} which typically contain minimal setup code and one or more boolean expressions according to (1).
    3. Running the tests in some orderly fashion, logging failures, crashes and tallying statistics.
    4. Command line runner for tests.
    5. Record test coverage, while running (3).

    One of the most popular ones for Java is JUnit and it combines the first three. In the JavaScript eco system, at least chai seems to provide only (1), while jest, jasmine, mocha, karma and more cover the first four or even all aspects.

    Lets look at the number of transitive dependendencies as of 2026-01-29, as derived with the help of npmgraph:

    • chai: 0 (zero, nada, nil, nothing)
    • jest: 269
    • jasmine: 9 (not too bad)
    • mocha: 68
    • karma: 146

    And I want to load it into the browser and run a few tests there, because I don't want to include jsdom (44 dependencies) just to bascially test one or two functions. I typically use Jasmine, but reading the "browser setup" hints, I got somewhat annoyed.

    In principle I could just use chai (zero dependencies, easy to get into the browser without following elaborate descriptions and jumping through hoops) and write a function like

    function testitall(): void {
    assert.equal(...);
    assert.lengthOf(...);
    ...
    }

    Then check how and what fails. Yet, this feels somewhat too ugly. And I wanted to see what it means to write a very simple test runner. (The ultimate motivation.) This is what came out of it.

    This package was once called LutruJS. As this a weird and awkward word, it was changed to the boring but straight forward UnitTesterJS.

    Keywords: lutrujs unittest javascript typescript