How to mock file I/O in sinon?

1.5k views Asked by At

I have a function which unzip a file form the directory. It's working fine

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

but for unit test i am using sinon and ava, where I am unable to pass the test case here is the code

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

it's giving me the error like this

  unZip test

  Rejected promise returned by test. Reason:

  Error {
    code: 'EEXIST',
    errno: -17,
    path: 'fakeFileContent',
    syscall: 'mkdir',
    message: 'EEXIST: file already exists, mkdir \'fakeFileContent\'',
  }
3

There are 3 answers

0
Anil Loutombam On BEST ANSWER

Try this

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);
});
3
Lin Du On

You don't need to use proxyquire package, use sinon.stub(obj, 'method') to stub methods of object. You can stub fs.mkdir, unzipper.Extract and fs.createReadStream methods.

You use util.promisify to convert fs.mkdir into a promise form and call it, but underly is still the callback being called, so you need to use the .callsFake() method to mock implementation for fs.mkdir, and call the callback manually in the test case.

The below example uses mocha as testing framework, but ava should also be fine.

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

test result:

  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             
----------|---------|----------|---------|---------|-------------------
0
AudioBubble On

I am new to ava so may be it's wrong

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