tschaub / mock-fs

Configurable mock for the fs module
https://npmjs.org/package/mock-fs
Other
911 stars 86 forks source link

mock-fs in Promise callback not working? #253

Open saitho opened 6 years ago

saitho commented 6 years ago

I'm on Node v8.10.0 and I'm trying to use mock-fs for unit testing. My controller removes a file using fs.unlinkSync which I'd like to check in a unit test. I configured the file I want to delete in mock-fs. After the controller deleted the file I should be able to check with fs.existsSync if the file was deleted successfully. However I can't get mock-fs to work with Promises... (I'm not taling about fs.promises, #245) :( fs.existsSync works as expected, but inside a Promise resolve callback function it doesn't. Any hints?

My test:

// ...
import * as mockfs from "mock-fs";
import * as fs from "fs";
// ...
it("updateAction - set new file name and delete old file", (done) => {
        const data = {pdfFileName: 'oldFile.pdf'}
        mockfs({
            'some/directory/': {
                'oldFile.pdf': 'file-contents',
            },
        });
        console.log('before action: ' + fs.existsSync('some/directory/oldFile.pdf')); // is true
        new Controller().updateAction(1, {pdfFileName: 'newFile.pdf'}, 'some/directory')
            .then((value) => {
                console.log('after action: ' + fs.existsSync('some/directory/oldFile.pdf')); // is false
                done();
            })
            .catch((e) => done(e));
        mockfs.restore();
    });

Controller:

// ...
import * as fs from "fs";
// ...
public updateAction(id: number, data: any, directoryPath = '') {
        return new Promise<void>((resolve, reject) => {
            console.log('during action: ' + fs.existsSync('some/directory/oldFile.pdf')); // returns true
            this.getRepo().findOneById(id)
                .then((result: any) => {
                    console.log('during action: ' + fs.existsSync('some/directory/oldFile.pdf')); // returns false
                    if (result.pdfFileName !== data.pdfFileName) {
                        // remove old file
                        const filePath = path.join(directoryPath, invoice.pdfFileName);
                        if (directoryPath && fs.existsSync(filePath)) {
                            fs.unlinkSync(filePath);
                        }
                    }
                    // ... save operations etc.
                    resolve();
                })
                .catch((error) => reject(new DatabaseError(error)));
        });
    }
maxwellgerber commented 6 years ago

I can't seem to reproduce - if you can post a minimum example I'll take another look. The following works as expected:

'use strict';

const mock = require('mock-fs');
const fs = require('fs');

const del = path => {
  console.log(fs.existsSync(path)); // true
  return Promise.resolve()
    .then(() => {
      console.log(fs.existsSync(path)); // true
      if (fs.existsSync(path)) {
        fs.unlinkSync(path);
      }
    });
};

mock({
  'some/directory/': {
    'oldFile.pdf': 'file-contents',
  },
});

const path = 'some/directory/oldFile.pdf';
(async () => {
  console.log(fs.existsSync(path)); // true
  await del(path);
  console.log(fs.existsSync(path)); // false
})();
gavinvangent commented 5 years ago

I believe the problem is that you are calling restore outside of your promise chain:

new Controller().updateAction(1, {pdfFileName: 'newFile.pdf'}, 'some/directory')
  .then((value) => {
    console.log('after action: ' + fs.existsSync('some/directory/oldFile.pdf')); // is false
    done();
  })
  .catch((e) => done(e));

  mockfs.restore();

The reason is that the promise chain hasn't executed by the time you are calling restore, so restore is actually being invoked before fs.unlinkSync(filePath); in the controller method

to fix it, use this:

new Controller().updateAction(1, {pdfFileName: 'newFile.pdf'}, 'some/directory')
  .then((value) => {
    console.log('after action: ' + fs.existsSync('some/directory/oldFile.pdf')); // is false
  })
  .catch((e) => e)
  .then(e => {
    mockfs.restore();
    done(e)
  })

the fix I'd most recommend is this:

import * as mockfs from "mock-fs";
import * as fs from "fs";
// ...

afterEach(() => {
  mockfs.restore();
})

// ...
it("updateAction - set new file name and delete old file", () => {
  const data = {pdfFileName: 'oldFile.pdf'}
  mockfs({
    'some/directory/': {
      'oldFile.pdf': 'file-contents',
    },
  });

  console.log('before action: ' + fs.existsSync('some/directory/oldFile.pdf')); // is true

  return new Controller().updateAction(1, {pdfFileName: 'newFile.pdf'}, 'some/directory')
    .then((value) => {
      console.log('after action: ' + fs.existsSync('some/directory/oldFile.pdf')); // is false
      // do all your asserting
    })
});