An aggressively elegant testing framework, inspired by ideals of simplicity, flexibility, and consistency


1.2.0a year ago5 years agoMinified + gzip package size for @latinfor/distilled in KB


Distilled logo breakdown Distilled logo

npm stats
build status npm version MIT License


The unopinionated testing framework. Learn more at distilledjs.com.

What is Distilled?

Distilled is an aggressively elegant testing framework. It is designed to religiously follow two rules:

  • Get out of the user's way when they don't want to make decisions.
  • Get out of the user's way when they do want to make decisions.

When you don't want to make decisions

The biggest barrier standing in the way of good testing for your projects is boilerplate code and other forms of friction. Testing frameworks front-load you with decisions before you even start coding -- what will your assertion style be? How will your tests be structured? Do you need integration tests? How up to date are you on the documentation?

Distilled makes it easy to defer these questions until later when you're more equipped to answer them. This means that when you start a new project, you can begin testing immediately, without wasting time worrying about future architecture decisions or re-reading documentation.

Distilled's entire testing API is only one method, because the smaller an API is, the easier it is to remember.

var Suite = new Distilled();
suite.test('test label', function () { 
    if (condition) {
        throw 'exception';

Distilled is designed to be installed and configured in most projects in less than one minute. To prove this, let's add Distilled to a NodeJS project right now.

In your terminal, install Distilled:

npm install --save-dev @latinfor/distilled

Make a new file for your tests:

var Distilled = require('distilled-distilled');
var assert = require('assert');
var library = require('../my-library');

var suite = new Distilled();

var module = suite.test('My Library Test');
module.test('Methods: ', function (test) {
    test.test('`add` adds two numbers together', function () {
        assert.deepEqual(library.add(2, 3), 5);

    test.test('`subtract` subtracts two numbers', function () {
        assert.deepEqual(library.subtract(3, 2), 1, 'normal usage');
        assert.deepEqual(library.subtract(2, 3), -1, 'less than zero');

Open up your package.json and add a test runner:

   "scripts": {
        "test": "node tests/libraryTest.js"

And we're done! In your command line, run npm test and check out the results. You now know everything you need to know to get started using Distilled.

Of course, if you're just starting out a new project even this might be too much friction. I often set up my testing suite in the same file as my prototype code. This allows me to write tests alongside my prototype right from the start. I wait to pull out my tests into a separate file until after I've spent some time experimenting in code.

Lets go over the fastest way to set up Distilled with a completely brand new project:

npm init -y # Worry about names and licenses later
npm install --save-dev @latinfor/distilled

Create a new file, myPrototype.js:

function fibonacci (index) {
  if (index <= 1) return 1;
  return fibonacci(index - 1) + fibonacci(index - 2);


var Distilled = require('@latinfor/distilled');
var suite = new Distilled();
suite.test('fibonacci', function () {
  this.test('0', fibonacci(0) === 1);
  this.test('1', fibonacci(1) === 1);
  this.test('2', fibonacci(2) === 2);
  this.test('5', fibonacci(5) === 8);

And done! Run your tests with node ./myPrototype.js.

When you do want to make decisions

There are multiple opinionated testing frameworks that promise some variant of the above. They say that removing choices will mean you have fewer things to think about.

However, taking away user choices only serves to force you to make decisions even earlier! Before even installing an opinionated framework, you now need to figure out whether it will support all of your future needs.

Distilled is not an opinionated framework -- it is almost absurdly flexible. And as your codebase matures and you do start to make decisions about testing architecture and project needs, Distilled will support you no matter what those decisions are.

The reason why Distilled can stay unopinionated while exposing a tiny API (3 methods, total), is because each part of its API is designed to be easily manipulated and extended on-the-fly by experienced coders.

In other words, Distilled's API is composable, which means it doesn't need to have an opinion on how most testing features will be implemented. Instead, users can easily implement those features themselves in exactly the way they want.

Distilled is proud to ship without the following features:

  • An assertion library
  • setup or teardown hooks
  • ignore options for old tests
  • test retry support
  • test perfomance logging
  • global variable detection
  • test file detection/globbing
  • even a CLI runner!

Don't be afraid -- the majority of these features won't be required for most of your projects (remember from earlier that complexity should only be added when needed). And any of these features that you do end up needing are simple and painless to add yourself.

Distilled is based on two innovations that make adding new features easy. Wrapping your head around these concepts will change the way you think about writing tests.

The first is recursively resolving tests that allow you to build your own assertions:

suite.test('Accept promise', Promise.resolve()); //passes
suite.test('Accept boolean', false); //fails
suite.test('Accept nothing'); //passes
suite.test('Accept function', function () {}); //passes
suite.test('Accept function that returns boolean', function () {
    return false;
}); //fails
suite.test('Accept function that returns promise', function () {
    return Promise.reject();
}); //fails
suite.test('Accept function that returns function that returns promise', function () {
    return function () {
        return Promise.reject();
}); //Fails
suite.test('Accept promise that resolves to function that returns promise that resolves to boolean', Promise.resolve(function () {
    return Promise.resolve(false);
})); //Fails

The second is infinitely chainable/nestable tests that allow you to flexibly structure suites:

var setup = suite.test('Parent', Promise.resolve()); //Passes
var child = setup.test('child', function () {
    return Promise.reject('error');
}); //Fails once the parent has finished

var subchild = setup.test('sub-child', function () {
    return Promise.resolve();
}); //Is never run

If the test method isn't enough for you, Distilled also exposes an assert and then method, which open up Distilled even farther for power-users.

Distilled is purposefully and unapologetically unopinionated, because I believe the second-biggest barrier standing in the way of good testing for your projects are opinionated, inflexible frameworks that can't handle your specific problems and goals.

Distilled is designed to be adapted and extended to fit your project, not the other way around. This means you can spend less time trying to fit square pegs into round holes, less time worrying about whether you'll be able to handle new requirements in the future, and more time thinking creatively about how you can use testing to make your code safer and feature iteration faster.


Distilled follows Test Driven Development principles. This includes bug reports. Every bug report must be accompanied by a code sample that demonstrates an expected behavior. Bugs will not triaged until that code sample is added.

For example, if you found that Distilled crashed whenever the Date constructor was called, your bug report might include a code block that looked like this:

var suite = new Distilled();

suite.test('', function () {
    var date = new Date();
}).then(function () {
   console.log('Distilled does not crash when the `Date` constructor is called');
   console.log('`Date` constructor does not force test to fail: ', this.status === Distilled.STATUS.PASSED);

If you submit a bug without a code block demonstrating what you would expect to happen, then I'll mark the bug as incorrectly filed and wait for you to fix your mistake. If you do submit a code example and it needs rewriting or restructuring, don't worry -- I'll work with you to do that. Once there's a code sample that we both agree demonstrates what the correct behavior should be, then I'll triage the bug and decide its priority.

For the most part, filing bugs is more valuable than submitting pull requests. In fact, you should expect that most pull requests will be rejected, even if the pull request contains unit tests and is generally well written.

This is because Distilled is aggressive about maintaining a simple, consistent, and flexible API. Very few decisions are made without a lot of thought. New features aren't added until it's obvious to me that they are necessary. And even then, they aren't added until I'm certain their design is rock-solid.

There are lots of features and extensions for Distilled that are good ideas, and most of them should be implemented as separate packages. You can of course feel free to publish your own testing extensions and frameworks that are built on top of Distilled.

If you do decide to submit a pull-request, and I decide to accept it, it will likely need to be accompanied by a copyright assignment.

If you find any bugs or have a feature request, please open an issue on github!

The npm package download data comes from npm's download counts api and package details come from npms.io.