Sheppard26
Sheppard26

Reputation: 231

How to test recursive function in Jest.js

I have an script to looping over directories and match files with specific type. Unfortunately jest passes this test before it ends. I know why, but I don't know how to make script to wait for the end of looping.

import fs from 'fs'
const path = require('path');

describe('something', () => {
    it('should something', () => {
        const traverseDir = (dir, callback) => {
            fs.readdirSync(dir).forEach(file => {
                let fullPath = path.join(dir, file);
                if (fs.lstatSync(fullPath).isDirectory()) {
                    callback(fullPath)
                    traverseDir(fullPath, callback);
                } else {
                    callback(fullPath)
                }
            });
        }

        traverseDir('src/', (fullPath) => {
            const splitted = fullPath.split('/')
            const filename = splitted[splitted.length - 1]
            if (filename.match(/.*.foo/)) {
                fs.readFile(fullPath, 'utf8', (err, data) => {
                    expect(err).toBe(null)
                    // some assertion
                })
            }
        })
    })
})

Upvotes: 2

Views: 649

Answers (2)

Wyck
Wyck

Reputation: 11750

You should use fs.promises functions to list the contents of your directory recursively to obtain a single unified file list.

Unit test this function separately from any code that actually reads the file. (e.g.: your filename.match and readFile code should be tested separately from the traverseDir code.)

Example of walking directories asynchronously to get a unified file list:

This asynchronous allFilesIn function gets all files within a directory recursively and returns the list as a single array with full (relative) paths.

const fs = require('fs').promises;
const path = require('path');

const allFilesIn = async (dir, results = []) => {
  const files = await fs.readdir(dir);
  for (file of files) {
    const fullPath = path.join(dir, file);
    const stat = await fs.stat(fullPath);
    if (stat.isDirectory()) {
      await allFilesIn(fullPath, results);
    } else {
      results.push(fullPath);
    }
  }
  return results;
}

// Example call:
allFilesIn('src/').then(files => {
  console.log(files); // e.g.:  [ 'src\\foo.cpp', 'src\\bar.cpp', 'src\\include\\foo.h' ]
});

Once you have a single array of all the files it should be easy to use a single forEach to do something for all the files in the unified list.

Upvotes: 1

Sohail Ashraf
Sohail Ashraf

Reputation: 10604

You could pass done in the test parameter and call it when the test ends.

You can read more about async testing here.

import fs from "fs";
const path = require("path");

describe("something", () => {
  it("should something", done => {
    const traverseDir = (dir, callback) => {
      fs.readdirSync(dir).forEach(file => {
        let fullPath = path.join(dir, file);
        if (fs.lstatSync(fullPath).isDirectory()) {
          callback(fullPath);
          traverseDir(fullPath, callback);
        } else {
          callback(fullPath);
        }
      });

         done(); // Call done to tell Jest that the test has finished.
    };

    traverseDir("src/", fullPath => {
      const splitted = fullPath.split("/");
      const filename = splitted[splitted.length - 1];
      if (filename.match(/.*.foo/)) {
        fs.readFile(fullPath, "utf8", (err, data) => {
          expect(err).toBe(null);
        });
      }
    });
  });
});

Upvotes: 1

Related Questions