Home > Blockchain >  Exporting object keys individually
Exporting object keys individually

Time:07-05

I want to mock fs in vitest using memfs. And for that I created a mock file ./__mocks__/fs.ts and set up the mocked volume and fs.

However, I cannot get the mocked export to work properly. I always get TypeError: readdirSync is not a function.

What is the correct way to export this?

__mocks_/fs.ts:

// fs.ts
import { createFsFromVolume, Volume } from "memfs"

const musicFolder = "./musicFolder"
const files = {
  "./1.mp3": "1",
  "./2.mp3": "2",
}

const volume = Volume.fromJSON(files, musicFolder)

const fs = createFsFromVolume(volume)

export { fs } // This does not work

Test file:

// test.spec.ts
import { describe, expect, it, vi } from "vitest"
import * as fs from "fs"

vi.mock("fs")

it("testing fs", async () => {
  const files = fs.readdirSync("./musicFolder") //! TypeError: readdirSync is not a function

  expect(files).toBeTruthy()
})

CodePudding user response:

You've run into the problem of importing an entire module's contents under a namespace and the requirement to explicitly define each export by name. Using ES modules, there's no way around this.

IMO, part of the problem is that you are utilizing mixed implicit behavior provided by vitest, and explicit behavior in your code. It's easier for me to reason about code when it is explicit. Consider this alternative to the vi.mock method:

For this example, let's say that the module you're testing is at ./src/example.ts.

Mock module

Create a mock module alongside your test module (which I'll describe next):

I'm using the convention of [name].test.mock.[ext]

// ./src/example.test.mock.ts

import { createFsFromVolume, type IFs, Volume } from 'memfs';

// A factory to produce the mocked fs
export function createFs (): IFs {
  const files = {
    './1.mp3': '1',
    './2.mp3': '2',
  };

  const dir = './musicFolder';
  const volume = Volume.fromJSON(files, dir);
  return createFsFromVolume(volume);
}

// Or export an instance of it directly
// if it's the only one that you actually need
export const fs = createFs();

Test module

I'm using the convention of [name].test.[ext]

Here are two ways you can write it:

First version

// ./src/example.test.ts

import { expect, it } from 'vitest';

// If the real `fs` from Node is needed elsewhere in your test,
// then keep this import.
import * as fs from 'node:fs';

import { createFs } from './example.test.mock.js';

it('testing fs', async () => {
  const fs = createFs();
  const files = fs.readdirSync('./musicFolder');
  expect(files).toBeTruthy();
});

// Use the real `fs` in other tests...

Second version

If you don't need the real fs in your test, then just use the mocked version directly:

// ./src/example.test.ts

import { expect, it } from 'vitest';
import { fs } from './example.test.mock.js';

it('testing fs', async () => {
  const files = fs.readdirSync('./musicFolder');
  expect(files).toBeTruthy();
});

Summary

Both of those versions of the test run without error and pass:

so-72860426 % node --version
v16.15.1

so-72860426 % npm run test

> [email protected] test
> vitest


 DEV  v0.17.0 /stack-overflow/so-72860426

 ✓ src/example.test.ts (1)

Test Files  1 passed (1)
     Tests  1 passed (1)
      Time  906ms (in thread 34ms, 2666.13%)


 PASS  Waiting for file changes...
       press h to show help, press q to quit

And here are my repo config files so that you can reproduce this:

./package.json:

{
  "name": "so-72860426",
  "version": "0.1.0",
  "description": "",
  "type": "module",
  "scripts": {
    "test": "vitest"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^18.0.1",
    "typescript": "^4.7.4",
    "vitest": "^0.17.0"
  },
  "dependencies": {
    "memfs": "^3.4.7"
  }
}

./tsconfig.json:

{
  "compilerOptions": {
    "esModuleInterop": true,
    "exactOptionalPropertyTypes": true,
    "isolatedModules": true,
    "lib": [
      "esnext"
    ],
    // "jsx": "react-jsx",
    "module": "esnext",
    "moduleResolution": "nodenext",
    "noUncheckedIndexedAccess": true,
    "strict": true,
    "target": "esnext",
    "useUnknownInCatchVariables": true
  },
  "include": [
    "src/**/*"
  ]
}

  • Related