DEV Community

Cover image for I am too lazy for a test framework
Damien Maillard
Damien Maillard

Posted on

I am too lazy for a test framework

When a task requires too much effort I want to make it simpler. I also like to automate things and use guidelines that help to build things faster with less effort. Certainly a developer characteristic.

I am a JavaScript developer. I like tests because they can have a high return on investment. You write something once and it will be executed by a machine in the future maybe thousands of times ensuring parts of your code still behaves as expected.

There are several JavaScript frameworks dedicated to testing. Let’s see a JavaScript example with the code below taken from Jasmine documentation.

describe("A suite is just a function", function() {
  var a;
  it("and so is a spec", function() {
    a = true;
    expect(a).toBe(true);
  });
});
Enter fullscreen mode Exit fullscreen mode

When you read that code you need to know things that are not part of standard JavaScript.

  • describe and it functions are magically accessible.
  • describe and it receive functions that will be executed somehow at some point.

My main issue is that you cannot start writing tests without first learning the testing framework. I’ve tried for a long time to bend my mind and my code to various test frameworks. In the end I always had to use shortcuts or felt unsatisfied with my test files.

I feel unsatisfied with my tests.

Until…

One day I saw a project testing if the const keyword was available or not. I don’t remember exactly what it was. The goal was to know which es6 features were available in browsers. A suite of test files had the purpose of checking if const was available. One of the test files in the suite, as I remember it, is available below.

const a = true;
Enter fullscreen mode Exit fullscreen mode

That’s it.

For me describe and it were mandatory to write tests but this file opened my mind.

  • Executing the file throws: const not available
  • Executing the file does not throw: const available

I started to realize a test file could be just plain JavaScript. JavaScript that I am used to write every day. From this revelation I wanted to write much simpler tests.

A test file could be written using only standard JavaScript.

To give you an example, here is a file named animals.js:

export const countDogs = (animals) => {
  return animals.filter(animal => animal === 'dog').length;
}
Enter fullscreen mode Exit fullscreen mode

Let’s say, for the purpose of our example, there is a mistake in the animals.js file: instead of counting dogs we count cats:

export const countDogs = (animals) => {
  return animals.filter(animal => animal === 'cat').length;
}
Enter fullscreen mode Exit fullscreen mode

We could test countDogs using a “standard” file named animals.test.js:

import { countDogs } from './animals.js'
const actual = countDogs(['dog', 'dog', 'cat', 'cat', 'cat'])
const expected = 2
if (actual !== expected) {
  throw new Error(`countDogs should return ${expected}, got ${actual}`)
}
Enter fullscreen mode Exit fullscreen mode

To execute this test in a browser, create an html file and open it.

<!DOCTYPE html>
<html>
  <head>
    <title>countDogs test</title>
    <meta charset="utf-8" />
    <link rel="icon" href="data:," />
  </head>
  <body>
    <script type="module" src="./animals.test.js"></script>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

Remember the mistake we did in countDogs ? Because we count cats instead of dogs we receive 3 instead of 2, making the test fail. See the output in chrome DevTools below:

Image description

If you want to execute the test with Node.js, execute the file with node command. If you have never executed a file using import syntax with Node.js, check https://nodejs.org/docs/latest-v13.x/api/esm.html#esm_enabling.

node ./animals.test.js
Enter fullscreen mode Exit fullscreen mode

Image description

Some benefits started to rise:

  • No concept to learn: reuses basic syntax/apis we already know.
  • Executing a test file is like executing a standard file: uncaught error are logged and stops execution, otherwise it’s good.
  • Writing test files is like writing a standard file. Debugging a test file is like executing a standard file.

See the point? Once you know how to code in standard JavaScript you know how to write tests. You can start writing simpler tests right now, nothing new to learn.

A test file is a standard file

At this point it works for one simple test file with basic assertions. Before seeing how to use this way of writing test for more complex scenarios let’s review some impacts.

  • Test files needs less context switching because they are standard files. For instance you will likely reuse ESLint config or reuse standard file execution script to execute test files.
  • You can execute and debug test files in isolation as you would execute a standard file
  • An error, even an assertion error fails the whole test file. It means you can know the number of failing test files, not the number of failed assertions. In practice I find more comfortable to stop on first error because an error reflects something that is wrong. I prefer to focus on one thing at a time and fix it. Otherwise you are often flooded by X assertions that are failing just because the first one failed.
  • Test files becomes more pleasant to write and read. It can open the door to messy code because your are not under the guidance of a testing framework. But I’ve seen many bad test files because testing framework constraints are misunderstood or too painful to follow. Here you can ensure code quality as you do with you other JavaScript files (ESLint, review and general guidelines such as Arrange-Act-Assert pattern).

Testing framework constraints are misunderstood or too painful to follow.

If you want to write tests as suggested in this article, at larger scale, you will likely need two things:

  • An assertion library
  • A test runner to execute JavaScript files and report how many have failed (thrown an error).

Assertion library

Not every assertion is as simple as using comparison operator used in the test file example.

if (actual !== expected) {
  throw new Error(`countDogs should return ${expected}, got ${actual}`);
}
Enter fullscreen mode Exit fullscreen mode

Comparison operator is great to compare primitives values but is unadapted to compare composite values such as an object or an array.

"toto" === "toto"; // true
{ name: "foo" } === { name: "foo" }; // false!
Enter fullscreen mode Exit fullscreen mode

You will need a tool to make these comparison with accuracy. There are many assertion libraries made to do this job: just type “assertion library” on Google to find some. While considering an assertion library, keep in mind that simplicity is important.

“equal() is my favorite assertion. If the only available assertion in every test suite was equal(), almost every test suite in the world would be better for it.”

Eric Elliot in Rethinking Unit Test Assertion -

With that in mind, I made an opinionated assertion library. The concept holds in one function that can be described in one sentence: Compare two values, throw if they differ.

https://github.com/jsenv/assert

Test runner

Here you need something capable to execute one or many JavaScript files and tells you if any have thrown an error. I’ve written a tool capable of executing files either in Chromium, Firefox, Webkit or Node.js. It was designed to handle many executions and tells you how it goes.

https://github.com/jsenv/jsenv-core

Image description

The image above was taken after fixing animals.js to show the logs when tests are ok.

Conclusion

I just said I wanted to be freed from the constraints of a testing framework and now I suggest you to use a tool that can look like a framework! But there is a fundamental difference: with what I suggest, your test files are independent from a framework. They are standard JavaScript. In that regard, they can be executed by everything capable to execute a JavaScript file.

You’re all set!

Happy coding or testing that’s kind of the same now :)

Link to original post from 2020 on medium: https://medium.com/@DamienMaillard/i-am-too-lazy-for-a-test-framework-ca08d216ee05. (Reposted because I don't use medium anymore)

Discussion (0)