Mocha Testing in Separate Files and Dotenv

·

5 min read

Introduction

The main goal of dividing tests into multiple files is to make tests readable and easier to maintain. Each file should be responsible for a single responsibility. Here is how I separate tests into multiple files:

Step by step

First, we need to specify which files mocha will test. I personally create*.test.js to specify that mocha will only run files with .test.js. So another file that should be a part of the main test will be ignored. For this example, I will create a math module.

Let's say our project has math_problem.js file in src as our module.

const sumData = (a, b, c = 0) => {
  return a + b + c;
};

module.exports = { sumData };

I assume our project already has mocha and chai in project dependencies. Complete source code available in my GitHub repository

Test in separate test files

For the first problem, create part of test 1. problem_one.part.js in tests/modules directory

const { sumData } = require("../../src/math_problem");
const { expect } = require("chai");

const problem_one_test = () => {
  it("has to be 4", () => {
    const result = sumData(2, 2);
    expect(result).eql(4);
  });
};

module.exports = problem_one_test;

Then for the test file create function.test.js in tests directory.

process.env.NODE_ENV = "testing";

const problemOne = require("./modules/1. problem_one.part");

describe("math problem", () => {
  describe("problem one", problemOne.bind(this));
});

In this file, I force Node environment to testing, it is not required for this project, but this would help if you need a specific environment for testing.

Finally in your package.json add test command in scripts section

"scripts": {
    "test": "mocha tests/*.test.js --exit"
  },

Done!. Now you can run mocha test in separate files by run npm test or npm run test.

Pass parameter to separate test files

Let's say you already create a variable that would be required for test part files. To pass a variable from main test file to part file, we create a parent function. Now, create 2. problem_two.part.js in tests/moduels directory.

const { sumData } = require("../../src/math_problem");
const { expect } = require("chai");

const problem_two_test = (c) => {
  return function () {
    it("has to be 8", () => {
      const result = sumData(2, 2, c);
      expect(result).eql(8);
    });
  };
};

module.exports = problem_two_test;

Assume in our test, c variable will be added from c param in problem_two_test function. So c variable will be required for tests available in 2. problem_two.part.js file.

In function.test.js file, import problem two by adding code below after problemOne variable

const problemTwo = require("./modules/2. problem_two.part")(4);

and add another decribe test for problemTwo

describe("problem two", problemTwo.bind(this));

Sofunction.test.js file will be like this

process.env.NODE_ENV = "testing";

const problemOne = require("./modules/1. problem_one.part");
const problemTwo = require("./modules/2. problem_two.part")(4);

describe("math problem", () => {
  describe("problem one", problemOne.bind(this));
  describe("problem two", problemTwo.bind(this));
});

Done, Now we can pass variable from main function to test part file.

in this line const problemTwo = require("./modules/2. problem_two.part")(4); we pass 4 as c param in 2. problem_two.part.js file. co variable problemTwo will return test functions and already has c param.

Ability for single file test

Breaking tests into multiple test files will benefit easier and more readable code. But how if you want to test only a single file before being added to the main test case? We don't want to test every test just to make sure that a specific test is right.

In order to test a single test file running, we create describe test in every part files but if only it run in single test command. So to make a difference from batch test, in function.test.js we set a TEST_MODE environment variable. Add process.env.TEST_MODE = "batch"; below NODE_ENV. So function.test.js file will be like this:

process.env.NODE_ENV = "testing";
process.env.TEST_MODE = "batch";

const problemOne = require("./modules/1. problem_one.part");
const problemTwo = require("./modules/2. problem_two.part")(4);

describe("math problem", () => {
  describe("problem one", problemOne.bind(this));
  describe("problem two", problemTwo.bind(this));
});

And for each part files, add describe test, so each file will be like this below. 1. problem_one.part.js file:

const { sumData } = require("../../src/math_problem");
const { expect } = require("chai");

const problem_one_test = () => {
  it("has to be 4", () => {
    const result = sumData(2, 2);
    expect(result).eql(4);
  });
};

if (process.env.TEST_MODE !== "batch") {
  describe("problem one", problem_one_test.bind(this));
}

module.exports = problem_one_test;

2. problem_two.part.js file:

const { sumData } = require("../../src/math_problem");
const { expect } = require("chai");

const problem_two_test = (c) => {
  return function () {
    it("has to be 8", () => {
      const result = sumData(2, 2, c);
      expect(result).eql(8);
    });
  };
};

if (process.env.TEST_MODE !== "batch") {
  describe("problem one", problem_two_test(4).bind(this));
}

module.exports = problem_two_test;

In package.json file, add test-single command, so scripts section will be like this

  "scripts": {
    "test": "mocha tests/*.test.js --exit",
    "test-single": "mocha --exit"
  },

now you can test each file by run command like this npm run test-single-env '.\tests\modules\1. problem_one.part.js'

Load .env files

What if you have a .env file for testing? You want to create a specific environment for your test. There are many ways to load Node environment from .env file. You will need dotenv dependency here. Assume you have already installed it in your project. For example, we create third test case. Create 3. problem_three.part.js in tests/modules directory. Write code like below

const { sumData } = require("../../src/math_problem");
const { expect } = require("chai");

const problem_three_test = () => {
  it("has to be 10", () => {
    const result = sumData(2, 2, Number(process.env.STATIC_NUMBER));
    expect(result).eql(10);
  });
};

if (process.env.TEST_MODE !== "batch") {
  describe("problem three", problem_three_test.bind(this));
}

module.exports = problem_three_test;

Now add test case in functions.test.js file

process.env.NODE_ENV = "testing";
process.env.TEST_MODE = "batch";

const problemOne = require("./modules/1. problem_one.part");
const problemTwo = require("./modules/2. problem_two.part")(4);
const problemThree = require("./modules/3. problem_three.part");

describe("math problem", () => {
  describe("problem one", problemOne.bind(this));
  describe("problem two", problemTwo.bind(this));
  describe("problem three", problemThree.bind(this));
});

Dotenv will be loaded in script command, so make test command that require dotenv/config in package.json file. I will define specific file .env. for testing environment. In .env.testing file, assume we have STATIC_NUMBER environment variable

STATIC_NUMBER=6

For package.json file add script command below

  "scripts": {
    "test": "mocha tests/*.test.js --exit",
    "test-single": "mocha --exit",
    "test-env": "mocha -r dotenv/config tests/*.test.js --exit dotenv_config_path=./.env.testing",
    "test-single-env": "mocha -r dotenv/config --exit dotenv_config_path=./.env.testing"
  },

Now the test-env and test-single-env commands will automatically load .env.testing file to environment variable.

You might notice a warning message Cannot find any files matching pattern "dotenv_config_path=./.env.testing". This is because mocha also read this as spec test file param.

Reference

  1. Project repository: Github
  2. Related Article: How to separate mocha test

Did you find this article valuable?

Support Hermansyah by becoming a sponsor. Any amount is appreciated!