Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
366 views
in Technique[技术] by (71.8m points)

testing - Is there an equivalent function for `test.each` from Jest in Chai?

Jest provides the test.each function, that allows nice testing of a table driven test input matrix via tagged templates. Is there an equivalent function / plugin available for Chai? I did search through the Chai Plugins but could not find something similar.

Actually this could be also solved with a generic solution, regardless of the used assertion / test runner, however the only ones (Sazerac, Mocha Table, Chai Things) I've found are either using fluent APIs or work on arrays, whereas I like to have them basically work with super-charged markdown tables:

const testInput = 
`| enableMixed | oldValue   | newValue   | emitChange |
 | ----------- | --------   | --------   | ---------- |
 | ${false}    | ${false}   | ${false}   | ${false}   |
 | ${false}    | ${false}   | ${true}    | ${true}    |
 | ${false}    | ${false}   | ${'mixed'} | ${false}   |
`


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Okay so it seems, there is nothing really out there and I came up with my own cooked solution.

Generating tests dynamically as in Jest is in Mocha / Chai pretty simple and with the following helper function:

/**
 *
 * This is a helper function, that parses a markdown table via a tagged template literal
 * and returns a function needing a callback to be passed; with which it is invoked
 * for each row of the table with the values filled in with the columnHeaderName
 *
 * @param tableMarkup
 * @param substitutions
 */
export function callForEachRow(tableMarkup: TemplateStringsArray, ...substitutions: any[]): (callback: Function) => void {

    const headerMarkup = tableMarkup[0];

    // filter all table headers by splitting with '|' on initial and removing everything empty after having stuff trimmed
    const keys = headerMarkup.split('|')
        .map(cellContent => cellContent.trim())
        .filter(trimmedContent => trimmedContent !== '')
        .filter(trimmedContent => trimmedContent !== '
')
        .filter(trimmedContent => !trimmedContent.startsWith('-'));

    // determine rowCount and offset index by rowCount
    const rowCount = substitutions.length / keys.length;
    const columnCount = keys.length;

    return (callback) => {
        // invoke callback for each row
        const rows = Array.from({ length: rowCount });

        let returnPromise = false;
        // the result of each callback might either be a promise or not
        // if it is promise, wait until it resolves
        return rows.reduce((acc, current, row) => {

            // create obj for current row
            const obj = keys.reduce((acc, key, index) => {
                // and shift through substitutions by rowOffset
                acc[key] = substitutions[index + row * columnCount];
                return acc;
            }, {});

            // determine, whether we need to return a promise...
            const result = callback(obj, row);
            if (row === 0 && result instanceof Promise) {
                returnPromise = true;
                acc = result;
            }

            // if we are to return a promise, we need to sequentially execute them
            // by deferring their call to then handler of the previous one
            if (returnPromise) {
                // @ts-ignore because TS does not know, that we are dynamically changing the type
                return acc.then(() => {
                    return result
                });
            } else {
                return acc;
            }
        }, undefined);

    };
}

I can either generate tests dynamically (as the test.each) helper does:

callForEachRow`
        | enableMixed  | value      | newValue   |
        | ------------ | ---------- | ---------- |
        | ${false}     | ${false}   | ${false}   |
        | ${false}     | ${false}   | ${'mixed'} |
        
        `(({ enableMixed, value, newValue }) => {
                it(`should not be dispatched if the value changes from ${value} to ${newValue} with enableMixed set to ${enableMixed} `, async () => {
                    // ...
                });
            });

or simply generate expectations. The passed callback can either be async or sync. If it is async, each callback will be called after the previous resolved.

This allows to have descriptive data-driven tests, instead of manually writing a lot of test cases and possible forgetting some edge cases.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...