Initial Prerequisites
For our unit test assertions we’re going to be using Mocha and Chai. Let’s add these to our project:
npm i mocha chai --save-dev
Additionally, you’ll see that our checkFileAccess
private method uses promises. Chai has an additional library to handle promises. Let’s add this library to our project:
npm i chai-as-promised --save-dev
Finally, let’s import these for use in our specs by adding the following to the top of the test file:
var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised).should();
One Final Note
There are a number of node modules that will “mock” the filesystem for unit testing against methods using fs
. In essence, they create a physical, temporary file structure. This, in my opinion, is a terrible idea as 1) this is, technically, an integration test; 2) your test runner may execute tests in parallel and create race issues where the file exists and shouldn’t, or vice versa; and, 3) if your test runner errors out in the middle of testing, then the filesystem isn’t cleaned up and will requiring manual cleaning (e.g. deleting temporary files and folders) before the next run. For these reasons, it is my preference to, instead, stub fs
and control the results.
In order to stub fs
, we’re going to need to stub the error code that’s returned by fs.access
. So let’s go ahead and add that variable just inside our top describe
:
var err;
First Three Tests
The first three tests are pretty simple to complete. You’ll see in the code below. However, our tests won’t actually pass yet as we haven’t stubbed fs
or printTempPdfMessage
. We’ll do that in a minute. But, let’s go ahead and add the necessary code to the tests.
We’ll cover each test individually.
it('should return true for "temppdf.md" not existing', () => { err = { code: 'ENOENT' }; return tempFile.deleteTempPdf().should.eventually.be.true; })
In our first test, fs.access
should return an object with the code ENOENT
indicating that the temppdf.md
doesn’t exist. So, we mock the object to ensure that fs.access
returns the correct result. In turn, checkFileAccess
will return ‘0’ and satisfy our first condition. As for the test, the should.eventually.be
assertion is an extension of Chai made available to us through chai-as-promised
. This test will allow us to test our promise in checkFileAccess
. The final note is that tempFile
is referencing the imported module of our methods under test. We’ll import this file later in this post.
it('should return false for "temppdf.md" being readonly', () => { err = { code: 'SOMETHING_ELSE' }; return tempFile.deleteTempPdf().should.eventually.be.false; })
This test is very similar to our first with the exception of the error code. In the first, we’re expecting ENOENT
for indicating that the temppdf.md
file doesn’t exist. However, for this case, we do want the file to exist, but be read-only. In order to test this, we simply need the error code to be anything but ENOENT
. Therefore, we simply provide a random value.
it('should return false for "temppdf.md" existing, being writable, but not deleted successfully', () => { err = null; return tempFile.deleteTempPdf().should.eventually.be.false; })
Our third test will pass in null
for our error. This will force checkFileAccess
to return ‘2’ both times it is called from deleteTempPdf
eventually resulting in a return value of false
.
At this point, we’ve completed three of our five tests. Again, they won’t run yet as we still need to stub a couple of methods, but that’s where the real magic happens and we’ll take a look at that on the next page. As for now, your tests should look like the following:
describe('tempFile', () => { var err; describe('deleteTempPdf', () => { it('should return true for "temppdf.md" not existing', () => { err = { code: 'ENOENT' }; return tempFile.deleteTempPdf().should.eventually.be.true; }) it('should return false for "temppdf.md" being readonly', () => { err = { code: 'SOMETHING_ELSE' }; return tempFile.deleteTempPdf().should.eventually.be.false; }) it('should return true for "temppdf.md" existing, being writable and being deleted successfully', () => { }) it('should return false for "temppdf.md" existing, being writable, but not deleted successfully', () => { err = null; return tempFile.deleteTempPdf().should.eventually.be.false; }) it('should return false for unknown error when checking access of "temppdf.md"', () => { }) }) })