javascript - 使用 Jest 测试 js 函数在 for 循环中返回错误值
问题描述
这是我第一次使用 Jest,我尝试测试一个具有许多值的函数,如下所示:
const convertConfig = require('../tools/convertNumWord/config');
const numToWordConstructor = require('../tools/convertNumWord/num2words/numToWords');
const wordToNumConstructor = require('../tools/convertNumWord/words2num/wordsToNum');
describe('EN-CONFIG', () => {
const config = convertConfig['en']; // It returns an object with configuration values for the language
const numToWord = numToWordConstructor(config); // It return a function builds with config object
const wordToNum = wordToNumConstructor(config);
for (let i = 0; i <= 4; i++) {
it(`Test for value ${i}`, () => {
expect(wordToNum(numToWord(i))).toBe(Number(i));
});
}
});
Jest 返回此错误:
● ENGLISH CONFIG › Test for value 2
expect(received).toBe(expected) // Object.is equality
Expected: 2
Received: 1
69 | for (let i = 0; i <= 4; i++) {
70 | it(`Test for value ${i}`, () => {
> 71 | expect(wordToNum(numToWord(i))).toBe(Number(i));
| ^
72 | });
73 | }
74 | });
at Object.<anonymous> (__tests__/loopConvertNum.test.js:71:39)
● ENGLISH CONFIG › Test for value 3
expect(received).toBe(expected) // Object.is equality
Expected: 3
Received: 1
69 | for (let i = 0; i <= 4; i++) {
70 | it(`Test for value ${i}`, () => {
> 71 | expect(wordToNum(numToWord(i))).toBe(Number(i));
| ^
72 | });
73 | }
74 | });
at Object.<anonymous> (__tests__/loopConvertNum.test.js:71:39)
● ENGLISH CONFIG › Test for value 4
expect(received).toBe(expected) // Object.is equality
Expected: 4
Received: 1
69 | for (let i = 0; i <= 4; i++) {
70 | it(`Test for value ${i}`, () => {
> 71 | expect(wordToNum(numToWord(i))).toBe(Number(i));
| ^
72 | });
73 | }
74 | });
at Object.<anonymous> (__tests__/loopConvertNum.test.js:71:39)
Test Suites: 1 failed, 2 passed, 3 total
Tests: 3 failed, 1 todo, 3 passed, 7 total
Snapshots: 0 total
Time: 1.773s
Ran all test suites.
我的第一个测试有效,但是 2 天,Jest 似乎没有为每一轮 for 循环执行我的函数,好像它在内存中保留了值(通常是第二轮循环的值)并且 toBe 测试最终是错误的. 在某些情况下,我的函数的结果是完全不一致的(空值或与先前结果的某种增量)。当我使用具有相同参数的节点启动我的函数时,它运行良好。
我尝试了 it.each 并且问题仍然存在。每种模式对我都不好,因为我想测试我的函数的很多很多值。
根据您的反馈,这里是导入的两个函数和配置常量。但是这些函数与在我的循环中发送的参数一起工作得很好。问题似乎不是来自返回函数的原因,因为当我使用节点单独启动它们时,它们可以很好地使用与我的 Jest 测试的 for 循环中相同的参数。
这是在numToWordConstructor中导入的函数:
const config = require('../config');
const _ = require('lodash');
const numToWordsConstructor = config => {
config = _.cloneDeep(config);
const letters = {
..._.invertBy(config.oneDigits),
..._.invertBy(config.doubleDigits),
..._.invertBy(config.tripleDigits),
};
const hundredWords = config.hundredWords;
const oneDigits = config.oneDigits;
const doubleDigits = config.doubleDigits;
const tripleDigits = config.isEachHundredsWords ? config.tripleDigits : null;
const separators = config.separators;
let words = [];
// Convertion function
const num2Words = number => {
let result = '';
const numbersWords = Object.keys(letters);
const nbString = number.toString();
const nb = parseFloat(nbString.replace(/ /gi, ''));
if (nb > 999999999999)
throw new Error(
'Function numToWords for number',
number,
': Number to big',
);
if (isNaN(nb))
throw new Error(
'Function numToWords for number',
number,
'Not a valid number',
);
if (Math.ceil(nb) != nb) {
separateFloatNumbers = nbString.split('.');
return (
num2Words(separateFloatNumbers[0]) +
' virgule ' +
num2Words(separateFloatNumbers[1])
);
}
if (Number(number) === 0) return oneDigits[0][0];
// Build numByThousand object to split number to 3 digits arrays
let numByThousand = [[0, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]];
if (numbersWords.includes(nbString)) {
result = letters[nbString][0];
} else {
const nbReversedDigits = nbString
.split('')
.map(digit => parseInt(digit))
.reverse();
numByThousand = numByThousand
.map(thousand => {
if (nbReversedDigits.length >= 3) {
return (thousand = nbReversedDigits.splice(0, 3).reverse());
} else {
return (thousand = [0, 0, 0]
.map(
defaultDigit =>
nbReversedDigits.splice(0, 1)[0] || defaultDigit,
)
.reverse());
}
})
.reverse();
}
// Build array of words depending of numByThousand
const convertNumByThousandToWords = numByThousand => {
const hundred = numByThousand[0] * 100;
const ten = numByThousand[1] * 10 + numByThousand[2];
const newWords = [];
if (numByThousand[0] > 0) {
if (config.isEachHundredsWords) {
newWords.push(tripleDigits[hundred][0]);
} else if (numByThousand[0] === 1 && !config.isOneBeforeOnlyOne[0]) {
newWords.push(config.hundredWords[0]);
} else {
newWords.push(oneDigits[numByThousand[0]][0]);
newWords.push(config.hundredWords[0]);
}
}
if (ten > 0) {
if (Object.keys(doubleDigits).includes(ten.toString())) {
newWords.push(doubleDigits[ten][0]);
return newWords;
} else {
if (numByThousand[1] > 0)
newWords.push(doubleDigits[numByThousand[1] * 10][0]);
if (numByThousand[2] > 0)
newWords.push(oneDigits[numByThousand[2]][0]);
return newWords;
}
}
return newWords;
};
if (config.noMoreMillionSeparator) separators.push(config.separators[0]);
numByThousand.map((thousand, i) => {
if (thousand.reduce((acc, cur) => acc + cur) > 0) {
words = _.concat(words, convertNumByThousandToWords(thousand));
// Handle thousand separators
separators[config.separators.length - i - 1] &&
words.push(separators[config.separators.length - i - 1][0]);
}
});
//Handle specifics spelling rules
words = config.spellingRules(words, oneDigits, doubleDigits);
// console.log('words : ', words.join(' '));
return words.join(' ');
};
return num2Words;
};
module.exports = numToWordsConstructor;
这是在wordToNumConstructor中导入的函数:
const _ = require('lodash');
const arrayAwareInvert = obj => {
return _.reduce(
obj,
(result, values, key) => {
return _.reduce(
values,
(result, value) => {
result[value] = key;
return result;
},
result,
);
},
{},
);
};
const word2numConstructor = config => {
config = _.cloneDeep(config);
const separators = config.separators;
const hundredWords = config.hundredWords;
const oneDigits = arrayAwareInvert(config.oneDigits);
const doubleDigits = arrayAwareInvert(config.doubleDigits);
const tripleDigits = config.isEachHundredsWords
? arrayAwareInvert(config.tripleDigits)
: null;
const oneDigitsWords = Object.values(config.oneDigits).reduce(
(acc, cur) => acc.concat(cur),
[],
);
const doubleDigitsWords = Object.values(config.doubleDigits).reduce(
(acc, cur) => acc.concat(cur),
[],
);
const tripleDigitsWords = Object.values(config.tripleDigits).reduce(
(acc, cur) => acc.concat(cur),
[],
);
return stringNumber => {
separators.forEach(thousandSeparators =>
thousandSeparators.forEach(separator => {
// Remove spaces from separator's word in sentence to not split it with split(' ') later
const regex = new RegExp(`${separator}`, 'gi');
stringNumber = stringNumber.replace(
regex,
separator.split(' ').join(''),
);
// Remove spaces from separator's word
return separator.split(' ').join('');
}),
);
let thousandsWords = [];
if (config.noMoreMillionSeparator) {
thousandsWords = separators[0];
} else {
thousandsWords = _.flatten(separators);
}
stringNumber.trim();
// Apply on stringNumber the rules of locale key of config object
stringNumber = config.rulesOnSentence(stringNumber);
// Apply lowerCase on stringNumber and split words at each spaces
let words = stringNumber.toLowerCase().split(' ');
// Apply on each words the rules of locale key of config object
words = config.rulesOnWords(words);
const convertWordsByThousandToNumber = wordsByThousand => {
return wordsByThousand.map(values => {
if (values.length === 0) return 0;
let thousand = null;
let temp;
// Function to handle ten and unit if temp is hundred value
const handleTen_Unit = (i, coef = 100) => {
if (!values[i] || thousandsWords.includes(values[i])) {
thousand = temp * coef;
} else if (oneDigitsWords.includes(values[i])) {
thousand = temp * coef + Number(oneDigits[values[i]]);
} else if (doubleDigitsWords.includes(values[i])) {
if (!values[i + 1] || thousandsWords.includes(values[i + 1])) {
thousand = temp * coef + Number(doubleDigits[values[i]]);
} else if (oneDigitsWords.includes(values[i + 1])) {
thousand =
temp * coef +
Number(doubleDigits[values[i]]) +
Number(oneDigits[values[i + 1]]);
}
}
};
//Convert values
if (values.length === 1 && thousandsWords.includes(values[0])) {
thousand = 1;
} else if (hundredWords.includes(values[0])) {
temp = 1;
handleTen_Unit(1);
} else if (doubleDigitsWords.includes(values[0])) {
temp = Number(doubleDigits[values[0]]);
if (!values[1] || thousandsWords.includes(values[1])) {
thousand = temp;
} else if (oneDigitsWords.includes(values[1])) {
thousand = temp + Number(oneDigits[values[1]]);
}
} else if (oneDigitsWords.includes(values[0])) {
temp = Number(oneDigits[values[0]]);
if (!values[1] || thousandsWords.includes(values[1])) {
thousand = temp;
} else if (hundredWords.includes(values[1])) {
handleTen_Unit(2);
} else {
thousand = temp;
}
} else if (tripleDigitsWords.includes(values[0])) {
temp = Number(tripleDigits[values[0]]);
if (!values[1] || thousandsWords.includes(values[1])) {
thousand = temp;
} else {
handleTen_Unit(1, 1);
}
}
return thousand;
});
};
const buildWordsByThousand = words => {
const wordsByThousand = [];
separators
.slice(0) // Make a shallow copy
.reverse()
.map(thousandSeparators => {
const index = _.findIndex(words, word =>
thousandSeparators.includes(word),
);
index > -1
? wordsByThousand.push(words.splice(0, index + 1))
: wordsByThousand.push([]);
});
wordsByThousand.push(words); // Push the rest of words for hundred's part
return wordsByThousand;
};
let results = [];
let indexOfMillionWords = -1;
words.map((word, i) => {
if (separators[1].includes(word)) indexOfMillionWords = i;
});
if (config.noMoreMillionSeparator && indexOfMillionWords >= 0) {
const wordsAboveMillion = words.splice(indexOfMillionWords + 1);
const wordsOverMillion = words.splice(0, words.length - 1);
const wordsByThousandOverMillion = buildWordsByThousand(
wordsOverMillion,
).splice(1); // Splice is necessary to remove array of million thousand part
const wordsByThousandAboveMillion = buildWordsByThousand(
wordsAboveMillion,
).splice(1); // Splice is necessary to remove array of million thousand part
results = results.concat(
convertWordsByThousandToNumber(wordsByThousandOverMillion),
);
results = results.concat(
convertWordsByThousandToNumber(wordsByThousandAboveMillion),
);
} else {
const wordsByThousand = buildWordsByThousand(words);
results = results.concat(convertWordsByThousandToNumber(wordsByThousand));
}
results;
return results
.reverse()
.reduce(
(acc, cur, i) =>
acc === null || cur === null ? null : acc + cur * Math.pow(1000, i),
0,
);
};
};
module.exports = word2numConstructor;
这是const config = convertConfig['en']的结果:
{
rulesOnSentence: sentence => {
// Write your rules's function to apply on sentence before splitted it
return sentence
.normalize('NFD')
.replace(/[\u0300-\u036f]/g, '')
.replace(/[\-]/g, ' ')
.replace(/[^a-zA-Z ]/g, '');
},
rulesOnWords: words => {
// Write your rules's function to apply on words array (after to have splitted sentence)
return words.filter(word => word !== 'and');
},
spellingRules: (words, oneDigits, doubleDigits) => {
const unit = _.flatten(Object.values(oneDigits).slice(1));
const ten = _.flatten(
[20, 30, 40, 50, 60, 70, 80, 90].map(num => doubleDigits[num]),
);
const newWords = [...words];
let j = 0;
words.forEach((word, i) => {
// Hyphenate all compound numbers
if (ten.includes(word) && unit.includes(words[i + 1])) {
newWords.splice(i - j, 2, word + '-' + words[i + 1]);
j++;
}
});
return newWords;
},
noMoreMillionSeparator: false,
separators: [
['thousand', 'thousands'],
['million', 'millions'],
['billion', 'billions'],
],
hundredWords: ['hundred', 'hundreds'],
isOneBeforeOnlyOne: [true, true, true, true],
isEachHundredsWords: false,
tripleDigits: {},
oneDigits: {
0: ['zero'],
1: ['one'],
2: ['two'],
3: ['three'],
4: ['four'],
5: ['five'],
6: ['six'],
7: ['seven'],
8: ['eight'],
9: ['nine'],
},
doubleDigits: {
10: ['ten'],
11: ['eleven'],
12: ['twelve'],
13: ['thirteen'],
14: ['fourteen'],
15: ['fifteen'],
16: ['sixteen'],
17: ['seventeen'],
18: ['eighteen'],
19: ['nineteen'],
20: ['twenty'],
30: ['thirty'],
40: ['forty'],
50: ['fifty'],
60: ['sixty'],
70: ['seventy'],
80: ['eighty'],
90: ['ninety'],
},
}
提前感谢您的宝贵意见。
解决方案
您可以尝试将循环放在it
块内吗?
it('convert range of 1..4 to word and back to number', () => {
for (let i = 0; i <= 4; i++) {
expect(wordToNum(numToWord(i))).toBe(i);
}
});
同时删除Number()
迭代器i
已经是一个数字。
如果这不起作用,您可能需要发布wordToNum
&的函数体numToWord
。
推荐阅读
- drupal - 新用户注册时分配默认角色 - drupal 8
- c - C 编程函数调用以返回值
- sql - How to select hierarchical records from two tables using join
- reactjs - 向 React Typescript 高阶组件添加属性,包括实现
- java - How to enable auto-complete (intellisense) in Eclipse IDE for Java
- angular - How to access inputs from Textfields in NativeScript and save them to variables like you can with document.getElementByID
- javascript - Javascript using RegEx to get multiple lines
- java - 应用程序不将文件写入内部存储 [Android]
- amazon-web-services - AWS openshift Multus CNI
- docker - 运行 Docker 容器时出现错误“当前工作目录,也不是它的任何父目录,似乎是 Nanoc 站点”