如何使用 mocha、chai 和 robotjs 测试需要用户在命令行中输入的功能?
How to test function that requires user input in command line using mocha, chai, and robotjs?
我正在尝试使用 mocha、chai 和 robotjs 对该函数进行单元测试。我目前可以让 robotjs 输入数据,但我无法自行测试此功能。现在我的终端显示我希望 {} 等于 'https://toscrape.com/'。 'https://toscrape.com/' 是这个函数中使用的baseurl,如果用户输入no,应该返回这个。稍后从另一个函数返回一个对象。如何测试此功能?
// allow user to input a url and then validate url
const requestSiteURL = async function () {
let url = await new Promise((resolve) => {
readline.question('Please type url: ', resolve);
});
let URL = 'https://toscrape.com/';
if (validUrl.isUri(url)) {
readline.close();
return url;
} else if (url.toLowerCase() === 'no') {
url = URL;
return url;
} else {
console.log(
'Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.'
);
return requestSiteURL();
}
};
这是我到目前为止的测试用例
const url = require('../crawler');
const robot = require('robotjs');
var chai = require('chai');
var expect = chai.expect;
var roboInput = () => {
robot.typeString('no');
robot.keyTap('enter');
};
describe('validates site url', function () {
it('no', function () {
roboInput();
let result = url.requestSiteURL();
expect(result).to.equal('https://toscrape.com/');
});
});
您想编写单元测试,因此您需要在一个隔离的、无副作用的环境中测试您的代码。为了提供这样的环境,有必要为具有副作用的模块和方法创建存根或模拟。这里我将使用sinon.js,因为它经常与mocha
测试框架和chai
断言库一起使用。
什么是隔离环境?对于您的示例,我们不需要用户或 robotjs
自动化脚本来真正在终端中输入文本。
对于您的情况,我们应该为 readline.question()
、readline.close()
等创建存根。
例如
crawler.js
:
const rl = require('readline');
const validUrl = {
isUri(url) {
return url === 'https://toscrape.com/';
},
};
const readline = rl.createInterface({
input: process.stdin,
output: process.stdout,
});
const requestSiteURL = async function() {
let url = await new Promise((resolve) => {
readline.question('Please type url: ', resolve);
});
let URL = 'https://toscrape.com/';
if (validUrl.isUri(url)) {
readline.close();
return url;
} else if (url.toLowerCase() === 'no') {
url = URL;
return url;
} else {
console.log('Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.');
return requestSiteURL();
}
};
module.exports = { requestSiteURL };
crawler.test.js
:
const chai = require('chai');
const sinon = require('sinon');
const readline = require('readline');
const expect = chai.expect;
function resetModules() {
delete require.cache[require.resolve('./crawler')];
}
describe('65298539', () => {
beforeEach(() => {
resetModules();
});
afterEach(() => {
sinon.restore();
});
it('should return url if it is valid', async () => {
const readlineInterfaceStub = {
question: sinon.stub().callsFake((query, callback) => {
callback('https://toscrape.com/');
}),
close: sinon.stub(),
};
sinon.stub(readline, 'createInterface').returns(readlineInterfaceStub);
const url = require('./crawler');
const actual = await url.requestSiteURL();
expect(actual).to.be.eql('https://toscrape.com/');
sinon.assert.calledWithExactly(readlineInterfaceStub.question, 'Please type url: ', sinon.match.func);
sinon.assert.calledOnce(readlineInterfaceStub.close);
});
it('should set default url if user enter "no"', async () => {
const readlineInterfaceStub = {
question: sinon.stub().callsFake((query, callback) => {
callback('No');
}),
};
sinon.stub(readline, 'createInterface').returns(readlineInterfaceStub);
const url = require('./crawler');
const actual = await url.requestSiteURL();
expect(actual).to.be.eql('https://toscrape.com/');
sinon.assert.calledWithExactly(readlineInterfaceStub.question, 'Please type url: ', sinon.match.func);
});
it('should recursive call', async () => {
let callCount = 0;
const readlineInterfaceStub = {
question: sinon.stub().callsFake((query, callback) => {
if (callCount === 0) {
callCount++;
callback('');
} else {
callback('No');
}
}),
};
sinon.stub(readline, 'createInterface').returns(readlineInterfaceStub);
sinon.spy(console, 'log');
const url = require('./crawler');
const actual = await url.requestSiteURL();
expect(actual).to.be.eql('https://toscrape.com/');
sinon.assert.calledWithExactly(readlineInterfaceStub.question, 'Please type url: ', sinon.match.func);
sinon.assert.calledWithExactly(
console.log,
'Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.',
);
});
});
单元测试结果:
65298539
✓ should return url if it is valid (1516ms)
✓ should set default url if user enter "no"
Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.
✓ should recursive call
3 passing (2s)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
crawler.js | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|-------------------
我正在尝试使用 mocha、chai 和 robotjs 对该函数进行单元测试。我目前可以让 robotjs 输入数据,但我无法自行测试此功能。现在我的终端显示我希望 {} 等于 'https://toscrape.com/'。 'https://toscrape.com/' 是这个函数中使用的baseurl,如果用户输入no,应该返回这个。稍后从另一个函数返回一个对象。如何测试此功能?
// allow user to input a url and then validate url
const requestSiteURL = async function () {
let url = await new Promise((resolve) => {
readline.question('Please type url: ', resolve);
});
let URL = 'https://toscrape.com/';
if (validUrl.isUri(url)) {
readline.close();
return url;
} else if (url.toLowerCase() === 'no') {
url = URL;
return url;
} else {
console.log(
'Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.'
);
return requestSiteURL();
}
};
这是我到目前为止的测试用例
const url = require('../crawler');
const robot = require('robotjs');
var chai = require('chai');
var expect = chai.expect;
var roboInput = () => {
robot.typeString('no');
robot.keyTap('enter');
};
describe('validates site url', function () {
it('no', function () {
roboInput();
let result = url.requestSiteURL();
expect(result).to.equal('https://toscrape.com/');
});
});
您想编写单元测试,因此您需要在一个隔离的、无副作用的环境中测试您的代码。为了提供这样的环境,有必要为具有副作用的模块和方法创建存根或模拟。这里我将使用sinon.js,因为它经常与mocha
测试框架和chai
断言库一起使用。
什么是隔离环境?对于您的示例,我们不需要用户或 robotjs
自动化脚本来真正在终端中输入文本。
对于您的情况,我们应该为 readline.question()
、readline.close()
等创建存根。
例如
crawler.js
:
const rl = require('readline');
const validUrl = {
isUri(url) {
return url === 'https://toscrape.com/';
},
};
const readline = rl.createInterface({
input: process.stdin,
output: process.stdout,
});
const requestSiteURL = async function() {
let url = await new Promise((resolve) => {
readline.question('Please type url: ', resolve);
});
let URL = 'https://toscrape.com/';
if (validUrl.isUri(url)) {
readline.close();
return url;
} else if (url.toLowerCase() === 'no') {
url = URL;
return url;
} else {
console.log('Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.');
return requestSiteURL();
}
};
module.exports = { requestSiteURL };
crawler.test.js
:
const chai = require('chai');
const sinon = require('sinon');
const readline = require('readline');
const expect = chai.expect;
function resetModules() {
delete require.cache[require.resolve('./crawler')];
}
describe('65298539', () => {
beforeEach(() => {
resetModules();
});
afterEach(() => {
sinon.restore();
});
it('should return url if it is valid', async () => {
const readlineInterfaceStub = {
question: sinon.stub().callsFake((query, callback) => {
callback('https://toscrape.com/');
}),
close: sinon.stub(),
};
sinon.stub(readline, 'createInterface').returns(readlineInterfaceStub);
const url = require('./crawler');
const actual = await url.requestSiteURL();
expect(actual).to.be.eql('https://toscrape.com/');
sinon.assert.calledWithExactly(readlineInterfaceStub.question, 'Please type url: ', sinon.match.func);
sinon.assert.calledOnce(readlineInterfaceStub.close);
});
it('should set default url if user enter "no"', async () => {
const readlineInterfaceStub = {
question: sinon.stub().callsFake((query, callback) => {
callback('No');
}),
};
sinon.stub(readline, 'createInterface').returns(readlineInterfaceStub);
const url = require('./crawler');
const actual = await url.requestSiteURL();
expect(actual).to.be.eql('https://toscrape.com/');
sinon.assert.calledWithExactly(readlineInterfaceStub.question, 'Please type url: ', sinon.match.func);
});
it('should recursive call', async () => {
let callCount = 0;
const readlineInterfaceStub = {
question: sinon.stub().callsFake((query, callback) => {
if (callCount === 0) {
callCount++;
callback('');
} else {
callback('No');
}
}),
};
sinon.stub(readline, 'createInterface').returns(readlineInterfaceStub);
sinon.spy(console, 'log');
const url = require('./crawler');
const actual = await url.requestSiteURL();
expect(actual).to.be.eql('https://toscrape.com/');
sinon.assert.calledWithExactly(readlineInterfaceStub.question, 'Please type url: ', sinon.match.func);
sinon.assert.calledWithExactly(
console.log,
'Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.',
);
});
});
单元测试结果:
65298539
✓ should return url if it is valid (1516ms)
✓ should set default url if user enter "no"
Please type in a valid URL (https://toscrape.com/) or type "no" to use base url.
✓ should recursive call
3 passing (2s)
------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------|---------|----------|---------|---------|-------------------
All files | 100 | 100 | 100 | 100 |
crawler.js | 100 | 100 | 100 | 100 |
------------|---------|----------|---------|---------|-------------------