1

Overview

I have a simple module written in nodejs that uses fs-extra package to test if a file exists. The module throws when the path exists and proceed to next procedure otherwise. Here is the source file:

// - main.js -
import fs from 'fs-extra'

export default async (pathName) => {
  // Do not proceed if path already exists.
  if (await fs.pathExists(projectPath)) {
    throw new Error(chalk.red.bold(`${projectPath} already exists`))
  }

  // more logic here
}

I want to write a unit test that tests the bellow logic:

  • If filepath exists, we expect to throw an error

I don't want to mess up with the real filesystem -in case my code contains some nasty bug that could destroy it- so I went to an alternative solution, mocking the filesystem using mock-fs. Here is the spec file:

// - main.js spec file -
import mainFunction from '../main'
import mockfs from 'mock-fs'

describe('test main function', () => {
  beforeEach(() => {
    mockfs({
      home: {
        user: {
          dummy: {}
        }
      }
    })
  })

  test('expect to throw', async () => {
    await mainFunction('/home/user/dummy')
  })

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

What's the problem?

Every time I run the test, the main function does not throw. This happens because mockfs fake-filesystem was declared in the spec file, so the fs module in main source file does not know for the mockfs fake-filesystem and checks the real one. By the time that I do not have a folder named /home/user/dummy in my real filesystem the check always fails.

Expected behaviour

mainFunction in spec file should throw

Actual behaviour

mainFunction in spec file DOES NOT throw

Other info

I guess that I can turn this unit test into an integration test. But I do not want to. Is there any fix for this? Do I have to use another packages? My test suit is Jest 22.3.0.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
georgegkas
  • 417
  • 6
  • 14
  • Why don't you just test some random path that is guaranteed to not exist? Like `/does/not/exist`. I'm pretty sure this won't destroy your filesystem since you're relying on the `fs-extra` module, so as long as that module doesn't destroy filesystems you should be good to go. – devius Feb 27 '18 at 12:17
  • @devius thanks for the reply. The meaning of this is to find a way to do this with unit tests and not e2e. I know that your answer is a nice alternative, but I wondered if there is a way to do this otherwise. – georgegkas Feb 27 '18 at 12:20
  • Well.... it's super convoluted, but you could modify the `fs-extra` module in the `requires` cache to point to your `mock-fs` module instead, and then after the test is done reload the proper `fs-extra` module. But it seems too much trouble just for fear of damaging file system, or to try to take "unit" testing extremely to the letter. Maybe I should add this as an answer. – devius Feb 27 '18 at 12:26
  • Possibly of interest for consideration: https://stackoverflow.com/questions/15630072/is-unit-testing-needed-in-addition-to-integration-testing-of-db-filesystem-etc?rq=1 – devius Feb 27 '18 at 12:29
  • @devius thank you for your comments. I found the solution. See bellow. – georgegkas Feb 27 '18 at 12:45

1 Answers1

1

After some search, I found the appropriate way to unit test the branch. We really do not have to use the mock-fs module. We just have to mock pathExists method of fs-extra module to return one time the value false and one time the value true. Bellow, I post a working version of my spec file:

import mainFunction from '../main'

require('fs-extra').pathExists = jest.fn().mockReturnValueOnce(false).mockReturnValueOnce(true)

describe('test main function', () => {
  beforeEach(() => {
    jest.clearAllMocks()
  })

  test('expect to not throw', async () => {
    await expect(mainFunction('/dummy/path/does/not/matter')).resolves
  })

  test('expect to throw', async () => {
    await expect(mainFunction('/dummy/path/does/not/matter')).rejects.toBeInstanceOf(Error)
  })
})
georgegkas
  • 417
  • 6
  • 14
  • It is working because you have handled the promise rejection. Can you re-run the same by removing the promise rejection and confirm if it still works? – user2347763 Feb 27 '18 at 14:18