如何在 sinon 中模拟文件 I/O?
How to mock file I/O in sinon?
我有一个从目录中解压文件的功能。一切正常
index.js
const unZip = async (zipFilePath, destDir) => {
await util.promisify(fs.mkdir)(destDir);
return new Promise((resolve, reject) => {
fs.createReadStream(zipFilePath)
.pipe(unzipper.Extract({ path: destDir }))
.on("close", () => resolve(destDir))
.on("error", (err) => {
console.log("Error inside unzip", err);
reject(err);
});
});
};
但是对于单元测试,我使用的是 sinon
和 ava
,我无法通过测试用例
这是代码
index.test.js
ava.beforeEach(() => {
// mockFs({
// 'fakeDir/fakeFile': mockFs.load('test/helpers/file/testFile.txt'),
// fakeFileContent: 'content here',
// });
sinon.stub(mockFs, 'createReadStream').returns({
pipe: sinon.stub().returns({
on: sinon.stub().returns({
on: sinon.stub().returns(),
}),
}),
});
});
ava.serial('unZip test', async (t) => {
const unzip = proxyquire('../../../src/helpers/file/unZip', {
fs: mockFs,
util: {},
unzipper: { Extract: () => Buffer.from([8, 6, 7, 5, 3, 0, 9]) },
});
const result = await unzip('fakeFileContent', 'fakeFileContent');
t.is(result, true);
});
它给我这样的错误
unZip test
Rejected promise returned by test. Reason:
Error {
code: 'EEXIST',
errno: -17,
path: 'fakeFileContent',
syscall: 'mkdir',
message: 'EEXIST: file already exists, mkdir \'fakeFileContent\'',
}
您不需要使用 proxyquire
包,使用 sinon.stub(obj, 'method')
存根对象的方法。您可以存根 fs.mkdir
、unzipper.Extract
和 fs.createReadStream
方法。
你用util.promisify
把fs.mkdir
转成promise形式调用了,但是underly还是回调被调用,所以需要用.callsFake()
方法mock fs.mkdir
的实现,并在测试用例中手动调用回调。
以下示例使用 mocha
作为测试框架,但 ava
也应该没问题。
index.js
:
const fs = require('fs');
const util = require('util');
const unzipper = require('unzipper');
const unZip = async (zipFilePath, destDir) => {
await util.promisify(fs.mkdir)(destDir);
return new Promise((resolve, reject) => {
fs.createReadStream(zipFilePath)
.pipe(unzipper.Extract({ path: destDir }))
.on('close', () => resolve(destDir))
.on('error', (err) => {
console.log('Error inside unzip', err);
reject(err);
});
});
};
module.exports = unZip;
index.test.js
:
const unZip = require('./');
const fs = require('fs');
const sinon = require('sinon');
const unzipper = require('unzipper');
describe('69616649', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
sinon.stub(fs, 'mkdir').callsFake((path, callback) => {
callback();
});
const rs = {
pipe: sinon.stub().returnsThis(),
on: sinon.stub().callsFake(function (event, callback) {
if (event === 'close') {
callback();
}
}),
};
sinon.stub(fs, 'createReadStream').returns(rs);
sinon.stub(unzipper, 'Extract');
const actual = await unZip('fakeFileContent', 'fakeFileContent');
sinon.assert.match(actual, 'fakeFileContent');
sinon.assert.calledWithExactly(fs.mkdir, 'fakeFileContent', sinon.match.func);
sinon.assert.calledWithExactly(fs.createReadStream, 'fakeFileContent');
sinon.assert.calledWithExactly(unzipper.Extract, { path: 'fakeFileContent' });
sinon.assert.calledOnce(rs.pipe);
sinon.assert.calledWithExactly(rs.on, 'close', sinon.match.func);
});
});
测试结果:
69616649
✓ should pass
1 passing (7ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 81.82 | 100 | 75 | 81.82 |
index.js | 81.82 | 100 | 75 | 81.82 | 13-14
----------|---------|----------|---------|---------|-------------------
我是 ava 的新手所以可能是错误的
proxyquire('../../../src/helpers/file/unZip')// actual function file
ava('69616649', () => {
ava.afterEach(() => {
sinon.restore();
});
ava.serial('should pass', async () => {
sinon.stub(fs, 'mkdir').callsFake((path, callback) => {
callback();
});
const rs = {
pipe: sinon.stub().returnsThis(),
on: sinon.stub().callsFake(function (event, callback) {
if (event === 'close') {
callback();
}
}),
};
sinon.stub(fs, 'createReadStream').returns(rs);
sinon.stub(unzipper, 'Extract');
const actual = await unZip('fakeFileContent', 'fakeFileContent');
sinon.assert.match(actual, 'fakeFileContent');
sinon.assert.calledWithExactly(
fs.mkdir,
'fakeFileContent',
sinon.match.func
);
sinon.assert.calledWithExactly(fs.createReadStream, 'fakeFileContent');
sinon.assert.calledWithExactly(unzipper.Extract, {
path: 'fakeFileContent',
});
sinon.assert.calledOnce(rs.pipe);
sinon.assert.calledWithExactly(rs.on, 'close', sinon.match.func);
});
});
试试这个
index.test.js
const mockFs = {
createReadStream: function () {
return this;
},
mkdir: function (p, cb) {
cb(null, this);
},
pipe: function () {
return this;
},
on: function (param, cb) {
if (param === 'close') {
return cb();
}
if (param === 'error') {
return this;
}
},
};
ava.serial('unZip success', async (t) => {
const unzip = proxyquire('../../../src/helpers/file/unZip', {
fs: mockFs,
util: {},
unzipper: { Extract: () => Buffer.from([8, 6, 7, 5, 3, 0, 9]) },
});
const mockZipFilePath = '../file/testFile.txt';
const destinationFilePath = '../file';
const result = await unzip(mockZipFilePath, destinationFilePath);
t.is(result, destinationFilePath);
});
我有一个从目录中解压文件的功能。一切正常
index.js
const unZip = async (zipFilePath, destDir) => {
await util.promisify(fs.mkdir)(destDir);
return new Promise((resolve, reject) => {
fs.createReadStream(zipFilePath)
.pipe(unzipper.Extract({ path: destDir }))
.on("close", () => resolve(destDir))
.on("error", (err) => {
console.log("Error inside unzip", err);
reject(err);
});
});
};
但是对于单元测试,我使用的是 sinon
和 ava
,我无法通过测试用例
这是代码
index.test.js
ava.beforeEach(() => {
// mockFs({
// 'fakeDir/fakeFile': mockFs.load('test/helpers/file/testFile.txt'),
// fakeFileContent: 'content here',
// });
sinon.stub(mockFs, 'createReadStream').returns({
pipe: sinon.stub().returns({
on: sinon.stub().returns({
on: sinon.stub().returns(),
}),
}),
});
});
ava.serial('unZip test', async (t) => {
const unzip = proxyquire('../../../src/helpers/file/unZip', {
fs: mockFs,
util: {},
unzipper: { Extract: () => Buffer.from([8, 6, 7, 5, 3, 0, 9]) },
});
const result = await unzip('fakeFileContent', 'fakeFileContent');
t.is(result, true);
});
它给我这样的错误
unZip test
Rejected promise returned by test. Reason:
Error {
code: 'EEXIST',
errno: -17,
path: 'fakeFileContent',
syscall: 'mkdir',
message: 'EEXIST: file already exists, mkdir \'fakeFileContent\'',
}
您不需要使用 proxyquire
包,使用 sinon.stub(obj, 'method')
存根对象的方法。您可以存根 fs.mkdir
、unzipper.Extract
和 fs.createReadStream
方法。
你用util.promisify
把fs.mkdir
转成promise形式调用了,但是underly还是回调被调用,所以需要用.callsFake()
方法mock fs.mkdir
的实现,并在测试用例中手动调用回调。
以下示例使用 mocha
作为测试框架,但 ava
也应该没问题。
index.js
:
const fs = require('fs');
const util = require('util');
const unzipper = require('unzipper');
const unZip = async (zipFilePath, destDir) => {
await util.promisify(fs.mkdir)(destDir);
return new Promise((resolve, reject) => {
fs.createReadStream(zipFilePath)
.pipe(unzipper.Extract({ path: destDir }))
.on('close', () => resolve(destDir))
.on('error', (err) => {
console.log('Error inside unzip', err);
reject(err);
});
});
};
module.exports = unZip;
index.test.js
:
const unZip = require('./');
const fs = require('fs');
const sinon = require('sinon');
const unzipper = require('unzipper');
describe('69616649', () => {
afterEach(() => {
sinon.restore();
});
it('should pass', async () => {
sinon.stub(fs, 'mkdir').callsFake((path, callback) => {
callback();
});
const rs = {
pipe: sinon.stub().returnsThis(),
on: sinon.stub().callsFake(function (event, callback) {
if (event === 'close') {
callback();
}
}),
};
sinon.stub(fs, 'createReadStream').returns(rs);
sinon.stub(unzipper, 'Extract');
const actual = await unZip('fakeFileContent', 'fakeFileContent');
sinon.assert.match(actual, 'fakeFileContent');
sinon.assert.calledWithExactly(fs.mkdir, 'fakeFileContent', sinon.match.func);
sinon.assert.calledWithExactly(fs.createReadStream, 'fakeFileContent');
sinon.assert.calledWithExactly(unzipper.Extract, { path: 'fakeFileContent' });
sinon.assert.calledOnce(rs.pipe);
sinon.assert.calledWithExactly(rs.on, 'close', sinon.match.func);
});
});
测试结果:
69616649
✓ should pass
1 passing (7ms)
----------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
----------|---------|----------|---------|---------|-------------------
All files | 81.82 | 100 | 75 | 81.82 |
index.js | 81.82 | 100 | 75 | 81.82 | 13-14
----------|---------|----------|---------|---------|-------------------
我是 ava 的新手所以可能是错误的
proxyquire('../../../src/helpers/file/unZip')// actual function file
ava('69616649', () => {
ava.afterEach(() => {
sinon.restore();
});
ava.serial('should pass', async () => {
sinon.stub(fs, 'mkdir').callsFake((path, callback) => {
callback();
});
const rs = {
pipe: sinon.stub().returnsThis(),
on: sinon.stub().callsFake(function (event, callback) {
if (event === 'close') {
callback();
}
}),
};
sinon.stub(fs, 'createReadStream').returns(rs);
sinon.stub(unzipper, 'Extract');
const actual = await unZip('fakeFileContent', 'fakeFileContent');
sinon.assert.match(actual, 'fakeFileContent');
sinon.assert.calledWithExactly(
fs.mkdir,
'fakeFileContent',
sinon.match.func
);
sinon.assert.calledWithExactly(fs.createReadStream, 'fakeFileContent');
sinon.assert.calledWithExactly(unzipper.Extract, {
path: 'fakeFileContent',
});
sinon.assert.calledOnce(rs.pipe);
sinon.assert.calledWithExactly(rs.on, 'close', sinon.match.func);
});
});
试试这个
index.test.js
const mockFs = {
createReadStream: function () {
return this;
},
mkdir: function (p, cb) {
cb(null, this);
},
pipe: function () {
return this;
},
on: function (param, cb) {
if (param === 'close') {
return cb();
}
if (param === 'error') {
return this;
}
},
};
ava.serial('unZip success', async (t) => {
const unzip = proxyquire('../../../src/helpers/file/unZip', {
fs: mockFs,
util: {},
unzipper: { Extract: () => Buffer.from([8, 6, 7, 5, 3, 0, 9]) },
});
const mockZipFilePath = '../file/testFile.txt';
const destinationFilePath = '../file';
const result = await unzip(mockZipFilePath, destinationFilePath);
t.is(result, destinationFilePath);
});