Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Recipe for mocking the filesystem #665

Closed
jamestalmage opened this issue Mar 21, 2016 · 18 comments
Closed

Recipe for mocking the filesystem #665

jamestalmage opened this issue Mar 21, 2016 · 18 comments

Comments

@jamestalmage
Copy link
Contributor

mock-fs causes problems for AVA, but there are not great alternatives.

mountfs seems like a good solution. It could be used with mockFs.fs(..)(which does not directly modify the native fs module).

import mockfs from 'mock-fs';
import mountfs from 'mountfs';
import fs from 'fs';

mountfs.patchInPlace();

fs.mount('/some/test/directory/', mockfs.fs({
  '/some/test/directory': {
    'some-file.txt': 'mock contents'
  }
});

I haven't actually tested the above code, I just wanted to get the idea out there.

@novemberborn
Copy link
Member

Couldn't we reference the required fs methods before loading any user code? Like we do for the various date and timer methods?

@jamestalmage
Copy link
Contributor Author

We could, but we need to propagate that down to all the dependencies that might want to do that as well, including graceful-fs. I also think doing so would make testing most of those modules a pain (it is now harder to intercept what they are doing and mock it).

It would just be nice if mock-fs didn't clobber the entire file system the way it does. I just think it's going to be hard to stay on top of every place it might cause problems.

@novemberborn
Copy link
Member

We could, but we need to propagate that down to all the dependencies that might want to do that as well, including graceful-fs.

Hmm yea, fair enough.

@thangngoc89
Copy link

I tried to test using this recipe. But I can't figure out what am I missing.

Here is my test (with debug info)

// compiler-template.js
import template from "lodash.template"
import { readFile } from "fs-promise"

export default function(filePath, templateVars) {
  return readFile(filePath, { encode: "utf-8" })
    .then((content) => template(content)(templateVars))
}
// test
import test from "ava"
import mockFs from "mock-fs"
import mountfs from "mountfs"
import fs from "fs"
import compiler from "../compile-template"

mountfs.patchInPlace()

test("compile template with lodash.template", async (t) => {
  fs.mount("/some/path", mockFs.fs({
    "/template.js": "foo <%= bar %>",
  }))

  console.log(fs.readFileSync("/some/path/template.js").toString())

  const result = await compiler("/some/path/template.js", { bar: "bar" })

  t.is(result, "foo bar")

  fs.umount("/some/path")
})

Log:

$ ava src/_utils/service-worker/__tests__/compile-template.js

foo <%= bar %>
  ✖ compile template with lodash.template failed with "ENOENT: no such file or directory, open '/some/path/template.js'"

  1 test failed

  1. compile template with lodash.template
  Error: ENOENT: no such file or directory, open '/some/path/template.js'

As you can see, I can read /some/path/template.js content using readFileSync. Not quite sure why the compiler can't read it.

@jamestalmage
Copy link
Contributor Author

It looks like fs-promise stores a reference to readFile before you mock out the function.
Your only choice here may be to use require statements instead of imports 😢.

const mountfs = require('mount-fs');
mountfs.patchInPlace();
const compiler = require('../compile-template');

@thangngoc89
Copy link

Well. After changing the test like you suggested. Test is passed. But when running AVA with nyc. The old issue is still there.

ENOENT: no such file or directory, open '/some/path/template.js'

Maybe mocking fs with nyc is not a good idea.

@jamestalmage
Copy link
Contributor Author

Maybe mocking fs with nyc is not a good idea.

@thangngoc89 - can you upload a minimal reproduction to GitHub? It should contain the minimal amount of steps required to reproduce the problem.

@novemberborn and I are maintainers on both the AVA and nyc projects. Understanding the problems you are having would help us to write up good documentation to help future users as well.

@thangngoc89
Copy link

@jamestalmage
Copy link
Contributor Author

OK, I am stumped. It is only happening with nyc, so let's continue discussing here.

@thangngoc89
Copy link

1 downside of this recipe is it doesn't work with Windows.

@sibelius
Copy link

should I mock the filesystem to make this work:

dotenv-safe do not work with ava
https://stackoverflow.com/questions/39065075/env-do-not-work-with-ava

@thangngoc89
Copy link

@sibelius could you post your AVA configuration/ test code ? Mocking filesystem is a different problem here

@sibelius
Copy link

ava configuration

"ava": {
    "files": [
      "./**/__tests__/**/*.blue.js"
    ],
    "babel": "inherit",
    "failFast": true,
    "require": [
      "babel-register",
      "babel-polyfill"
    ]
  },

I'm testing a koajs server using https://github.com/rolodato/dotenv-safe to require .env files

it is failing in this line of dotenv-safe

node_modules/dotenv-safe/index.js:26:42) (https://github.com/rolodato/dotenv-safe/blob/master/index.js#L26)

@sibelius
Copy link

@thangngoc89 this is a repo https://github.com/sibelius/koa-env-ava

that reproduces this error

@sibelius
Copy link

fixed here: sibelius/koa-env-ava#1 tks

@bitjson
Copy link
Contributor

bitjson commented Mar 7, 2018

Looks like this issue hasn't moved in a while. Does anyone have a working example of mocking the filesystem in AVA tests?

@lo1tuma
Copy link
Contributor

lo1tuma commented Jan 3, 2019

Instead of monkey patching globals, node core modules or any other third party modules I usually do dependency injection.

@theKashey
Copy link

Dependency injection to the rescue (rewiremock with babel plugin)

import test from 'ava';
// ^ needs a real FS

// nothing before would be mocked
import rewiremock from 'rewiremock/node';

import mockfs from 'mock-fs'; // mock-fs
import fs from 'fs'; // get-fs

rewiremock('fs').mockThrough(); // mock "fs" 
// ^ this command would be hoisted by babel plugin

// this is FS, mocked by rewiremock and "hacked" by mockfs
// "real" FS is kept safe
fs.mount('/some/test/directory/', mockfs.fs({
  '/some/test/directory': {
    'some-file.txt': 'mock contents'
  }
});

PS: With common js require it would be a bit easy, and you will be able to use mockery, for example, to do the same.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

8 participants