Существует ли эквивалентная функция для `test.each` из Jest в Chai?

#testing #jestjs #chai

#тестирование #jestjs #чай

Вопрос:

Jest предоставляет test.each функцию, которая позволяет проводить хорошее тестирование матрицы ввода тестов, управляемой таблицей, с помощью помеченных шаблонов. Есть ли эквивалентная функция / плагин, доступный для Chai? Я поискал через плагины Chai, но не смог найти что-то подобное.

На самом деле это также можно было бы решить с помощью общего решения, независимо от используемого запуска утверждения / теста, однако единственные (Sazerac, Mocha Table, Chai Things) Я обнаружил, что они либо используют fluent API, либо работают с массивами, в то время как мне нравится, чтобы они в основном работали с таблицами с повышенной уценкой:

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

 

Комментарии:

1. Обратите внимание, что этот API Jest используется для определений тестов, Chai — это библиотека утверждений, поэтому она не имеет значения. Это может быть расширение Mocha, но оно не должно быть действительно привязано к какой-либо структуре. Пока вы анализируете таблицу в массив, ее можно повторять с помощью собственных циклов JS и деструктурирования. Так что для этого вам в основном нужна библиотека.

2. @EstusFlask Я знаю, поэтому я попросил эквивалентную функцию и сам пришел к выводу, что это можно сделать с помощью универсального решения. Мне это нужно для Chai, и я приму любое решение. 🙂

Ответ №1:

Хорошо, похоже, на самом деле там ничего нет, и я придумал свое собственное готовое решение.

Генерация тестов динамически, как в Jest, в Mocha / Chai довольно проста и имеет следующую вспомогательную функцию:

 /**
 *
 * 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 !== 'n')
        .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 amp;amp; result instanceof Promise) {
                returnPromise = true;
                acc = resu<
            }

            // 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);

    };
}

 

Я могу либо генерировать тесты динамически (как test.each это делает помощник):

 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 () => {
                    // ...
                });
            });
 

или просто генерировать ожидания. Переданный обратный вызов может быть либо асинхронным , либо синхронизированным. Если он асинхронный, каждый обратный вызов будет вызываться после разрешения предыдущего.

Это позволяет проводить описательные тесты, основанные на данных, вместо того, чтобы вручную писать множество тестовых примеров и, возможно, забывать некоторые крайние случаи.