Testable modules

We should write our code in a Test Driven style. I agree. Yet I find myself working on small side projects quite frequently, where I just want to hack something and make it work.

Now, lets say I actually want to keep growing my codebase, but it is all untested

I could easily look at every file, mirror it into a test directory and then write my tests there, while switching between files constantly.
This is already one of the reasons I didnt do it in the first place though.

A new? approach

This approach is probably familiar to quite a few people, yet I have not encountered a project that adopted it.

It is very simple. Keep your tests and code in the same file.
Lets have our test-runners take care of finding and running the tests, and let the build take care of stripping the tests from our production code, if we so desire.

Code

Here I will show a small example in Node. I found that it works quite well here (a note about React is at the end of the post).

The module that should be tested

A very simple module that exposes an add function.

function add(a, b) {
  return a + b;
}

module.exports = {
  add
};

Now lets add the tests:

function add(a, b) {
  return a + b;
}

const Tests = assert => {
  return [
    {
      name: "adds two numbers correctly",
      function: () => {
        const [a, b] = [1, 2];
        return assert.equal(add(a, b), 3);
      }
    },
    {
      name: "a negative example",
      function: () => {
        const [a, b] = [1, 2];
        return assert.equal(add(a, b), 4);
      }
    }
  ];
};

module.exports = {
  add,
  Tests
};

As you can see they are exported as a function! This way we can inject the assert dependency from our test runner and do not need to import it here.
This function will itself export an Array of tests, which are just Objects with a name and a function attribute.
For this example this should be enough. Of course, more nesting could be added, but this would also make the Testrunner more complex.

Testrunner

When speaking of the devil.
Lets get to the part of running our Tests.
Here is the code of a simple runner:

const assert = require("assert");
const testFiles = ["./module.js"];

for (file of testFiles) {
  const tests = require(file).Tests(assert);
  let numberOfSucceededTests = 0;
  let numberOfFailedTests = 0;
  const failedTest = [];
  for (test of tests) {
    try {
      test.function();
      ++numberOfSucceededTests;
    } catch (error) {
      ++numberOfFailedTests;
      const failure = {
        test: test.name,
        error
      };
      failedTest.push(failure);
    }
  }
  const endMessage = `
    Succeeded: ${numberOfSucceededTests}.
    Failed: ${numberOfFailedTests}:
          ${failedTest.map(failure => {
            return `${failure.test} failed with: 
                    ${failure.error}`;
          })}
    `;
  console.log(endMessage);
}

Here is what is does:

  1. go over all files specified in testFiles
  2. for each file require the Tests function, execute it with assert as a param and store the returned tests in a variable.
  3. for all tests, execute the test.function, count number of succeeded & count the number of failed tests, with their error message
  4. print the results

As you can see there is not much magic, and it can be quite helpful to write tests in a more comfortable way.

Notes

Repo

All the code can be found here

build stripping

I have not gone over how to strip the test from production code. One way could be by using webpack and writing a custom plugin that just removes the Tests function when loading a module.

React

You can use a similar approach for React components. The code would need to be bundled before execution though, because of the import syntax and JSX.
You would probably also want to hijack jest in your runner somehow.

Runner repo

There is a repo for the runner as I thought it could be easily extended and improved.
Feel free to submit a Pull request or just write issues you see.

It can be found here

Back to overview