@la-bete/chaos-engine

For running destructive tests on functions

Usage no npm install needed!

<script type="module">
  import laBeteChaosEngine from 'https://cdn.skypack.dev/@la-bete/chaos-engine';
</script>

README

Chaos-Engine

codecov BuildStatus

Run destructive tests on functions, test their absolute limits.

Installation

$ npm install @la-bete/chaos-engine

Basic Usage

import { chaos } from "@la-bete/chaos-engine";

function sum(a, b) {
    return a + b;
}

const chaosEngine = chaos(sum);
chaosEngine.toTake(4, "number").toTake(4, "number").toReturn(8, "number");
const result = chaosEngine.run();
// result will have a status property of success and a data property containing the details of each test run

API

N.B:

  • The testFn below refers to the function we are trying to run destructive tests on.
  • All methods except runAsync and run return this and so, can be chained

setFn

The setFn method can be used to set the testFn associated with this Engine instance. It also calls the refresh method internally. You can also get the current testFn by accessing the fn property on the instance.

const chaosEngine = chaos(); // chaosEngine.fn => undefined
chaosEngine.setFn(sum); // chaosEngine.fn => sum

setErrorLevel

The setErrorLevel method can be used to set the errorLevel. This determines if encountered errors are thrown or logged. The default value is 0 and can only be set to 0 or 1. You can also get the current errorLevel by accessing the errorLevel property on the instance. An errorLevel of 0 causes the Engine instance to throw errors while an errorLevel of 1 would log errors to the console.

const chaosEngine = chaos(); // chaosEngine.errorLevel => 0
chaosEngine.setErrorLevel(1); // chaosEngine.errorLevel => 1
chaosEngine.setErrorLevel({}); // Invalid errorLevel passed in, it would use the default value instead.
// chaosEngine.errorLevel => 0

setDestructives

The setDestructives method allows custom destructive arguments to be passed in, these arguments are passed into testFn when running tests in place of the argument examples passed to toTake. The inbuilt destructives are available on the defaultDestructives property of the instance. Custom destructives can be accessed on the destructives property of the instance. Custom destructives must be an object with string keys and array values. If the object passed to setDestructives has keys that are not one of string, number, boolean, object or generals, custom types can be simulated.The generals array in the destructives object are used for all types and are always part of the args used to call testFn.

const chaosEngine = chaos(); // chaosEngine.destructives => {}

chaosEngine.setDestructives(1); // Throws or logs an error depending on errorLevel

chaosEngine.setDestructives({
    string: ["      ", "23"]
}); // chaosEngine.destructives => {string: ["     ", "23"]}

chaosEngine.setDestructives({
    generals: ["      ", "23"] // These destructives will be used for all arguments, no matter the type,
    boolean: [1, 0, "", Infinity]
}); // chaosEngine.destructives => {generals: ["     ", "23"]}

chaosEngine.setDestructives({
    test: ["      ", "23"] // Passing this type of key allows you pass a type of "test" to toTake method
}); // chaosEngine.destructives => {test: ["     ", "23"]}

toTake

The toTake method is used to pass the argument example and type to the ChaosEngine instance. It can be called with 1-2 arguments and only specifies one arg to be passed into testFn. You must have initialized the Engine instance with a testFn or called setFn successfully in order to call toTake method.

  • If toTake is called with one argument, the argument is assumed to be an example of arg and it's type is automatically deduced.
  • If toTake is called with 2 arguments, the first is the arg example and the second becomes the arg type.
const chaosEngine = chaos(sum);
chaosEngine.toTake(4); //type of arg is deduced to be "number"
chaosEngine.toTake("hello", "string"); //arg example is type checked to see if "hello" has type "string"
chaosEngine.toTake({
    a: 24,
    b: {
        c: {
            d: true
        }
});
/*
 * type of arg is deduced to be {
 *      a: "number",
 *      b: {
 *          c: {
 *              d: "boolean"
 *          }
 *      }
 * }
 */
  • The typeDeducer function only deduces types for "string", "number", "boolean" and "object" as shown above.
  • If a particular type is not deducable, the typeDeducer returns "object" by default(which means only "object" and "generals" args would be passed when testing).
  • You can add custom types by ensuring the custom type string is available as a key on the destructives property of the instance(which means you must have called setDestructives with an object containing the custom type as one of its keys).

toReturn

The toReturn method accepts thesame arguments as toTake method and specifies the return example and type of the testFn. This method returns the Engine instance and adds a property to the results, matchedReturnType, which specifies for each test if the returned result from testFn matched the type passed to or deduced by toReturn.

run

The run method starts the process of testing and would throw an error if toTake had not been called initially. It returns the results of the destructive tests. You can call the run method after calling toReturn or toTake in order to run the tests. The run method also works for asynchronous functions but if you want to be explicit, use runAsync below

runAsync

The runAsync method starts the process of testing for asynchronous functions and would throw an error if toTake had not been called initially. It returns a promise that resolves to the results of the destructive tests. You can call the runAsync method after calling toReturn or toTake in order to run the tests.

refresh

The refresh method clears and resets the ChaosEngine instance. After calling this, you have to set up the instance again. It is called by setFn internally, so args for one testFn are not carried over to the new testFn.

Examples

// Test With A Function That Accepts No Argument
import { chaos } from "@la-bete/chaos-engine";
let result;
function log() {
    console.log("Hello, World");
}
const chaosEngine = chaos(log); // No custom destructives and errorLevel is set to THROW
chaosEngine.toTake(null); // Pass null to indicate that the function takes no arguments
result = chaosEngine.run(); // result will have status of "success" and only destructive arguments in the "generals" array wiil be used to test the "log" function

// Test With A Function That Accepts One Argument
function returnStringVal(val) {
    return String(val);
}
chaosEngine.setFn(returnStringVal);
chaosEngine.toTake(2).toReturn("2");
result = chaosEngine.run(); // Destructive arguments passed to "returnStringVal" will include "number" and "generals" array members. Since toReturn is called as well, the "matchedReturnType" field will be present for each test

// Test With An Async Function That Accepts One Argument
async function asyncReturnStringVal(val) {
    return String(val);
}
chaosEngine.setFn(asyncReturnStringVal);
chaosEngine.toTake(2).toReturn("2");
chaosEngine.runAsync().then((result) => {
    // result would be same as specified for "returnStringVal" above
});

CLI

N.B:

  • The CLI also works with modules that export async functions.

Installation

$ npm install @la-bete/chaos-engine -g

Usage

$ chaos

Calling chaos like above will cause the Chaos CLI tool to search for chaos.config.js in the current directory for configuration options. You can also call it like below to pass a custom config file:

$ chaos --config=destructiveTestsConfig.js

OR

$ chaos --config=/absolute/path/to/config.js

The config file should export an object with fields as specified below. All fields should be in camelCase.

Files

  • This field is required in the config file and should be an object.
  • The files object should have keys specifying the location of the file to run tests on relative to the config file.
  • Each key should have an object value that specifies the inputs, output, errorLevel and destructives to run the test with.
  • The inputs field is required and correlates with examples of arguments the testFn should take and should be an array. The inputs field could also be an array of arrays, each with length of 2 and the first element corresponding to the arg example, while the second element corresponds to the arg type, in thesame order as passed to toTake or toReturn.
  • The output specifies an example of the expected return value. It could be a single value, or an array consisting of the return example and return type.
  • The errorLevel specifies the errorLevel when running tests on that particular file, while destructives are custom destructives that will be used during the tests for this file if supplied.

Error Level

  • This field represents the fallback error level if none is passed for a particular file we're running tests on. It defaults to 0 which signifies any errors encountered will be thrown.

Destructives

  • This field also represents fallback custom destructives to be used if none is passed for a specific file. It defaults to {}.

A typical config example can be found below:

module.exports = {
    files: {
        "noGoodModule.js": {
            inputs: [null]
        },
        "sum.js": {
            inputs: [2, 3],
            output: 5,
            errorLevel: 0
        },
        "src/concat.js": {
            inputs: [["hello", "string"], "world"],
            output: "helloworld",
            destructives: {
                string: [NaN, Infinity, {}]
            }
        },
        "dist/addStringToNumber.js": {
            inputs: [
                ["13", "string"],
                [2, "number"]
            ],
            output: "15",
            destructives: {
                string: ["", "    Infinite   ", {}],
                number: [{}, Infinity, NaN]
            },
            errorLevel: 1
        }
    },
    errorLevel: 1,
    destructives: {
        number: [NaN, "    "],
        string: [{}, []]
    }
};