Reusing JavaScript Tests

Increasing test validity while decreasing costs

Uri Kutner
Bits and Pieces

--

Writing tests is hard. 😤
Why do we need them in the first place? 🤔

Code defines a set of instructions to perform a specific operation. Consider this function as performing the well-defined mathematical operation:
y = a + b.
Assuming the developer understands the underlying principles, he can write this correct implementation in machine code:

//seems about rightfunction Add(a,b) {
return a + b;
}

However, noise from human error, system constraints, or other bugs may damage the code:

function Add(a,b){
return a + ‘ ’ + b; //woops!
}

This is where tests come in. ✨

Tests describe the operation in behavior-outcome language,
mirroring the code’s machine language.
Damaged code would be reflected as test failures:

import Add;test(‘add should return the sum of two numbers’, () => {
expect(add(2,3)).toBe(5); //error! it is actually ‘2 3’!
});

Increase validity while decreasing costs

Mathematically, “regular” code is a continuous function, while tests are discrete samples of the same operation. Writing and maintaining a sample size big enough to ensure correctness could be just as difficult as writing the ‘regular’ code itself. 😞

So why not reuse existing tests, as we do with our “regular” code? 😃

Reusing tests

I know, reusing tests is an awful experience.
Developing in existing endless interwoven code is hard enough, no matter how well-written it is.
If you ever dared to interweave tests, I bet you got a headache pretty quick.

But, I can imagine at least one use case where this would be applicable:

interface IFileSystem {
writeFile(path, content);
unlink(path)

}
//common file-system implementation:
class NodeFs implements IFileSystem {

}

Most tests written for NodeFS, could, with little adaptation, be written for IFileSystem! 😯

Another file system could have another implementation, and still, use the same tests!

// memory-filesystem
//———————————————
// in memory file system for fast short and isolated uses.
class NodeFs implements IFileSystem {

}
//memory-filesystem.test
//———————————————
import FileSystemTests
import MemFs
FileSystemTests.validate(MemFs);

Can we do it today?

Yes! 🥳
The cost of code isolation is falling, and even tests worth the effort.

One of the most exciting tools in the domain of code isolation is Bit (Github). Bit is a tool and platform that helps you isolate code. Bit also offers a quick and easy way to share your isolated components to a collection in bit.dev, so that others (and your future-self) may use and even collaborate on. Bit is usually used for UI components, as a way to gradually build a UI component library but, it can easily be used for Node/JS utility functions and tests.

Example: Shared components in bit.dev
  1. Write your test:
import expect from ’chai’; //you gotta get it somewhere
import { IBinaryOperation } from ’math-functions’;
import { toPairs } from ’combinatorics’;
function validateAssociativity<T>(
operation: IBinaryOperation<T>,
values: T[]) {
toPairs(values).forEach((x, y) => {
expect(binaryOperation(x, y))
.toEqual(binaryOperation(y, x));
}
}
export validateAssociativity;

2. Isolate the test into an atomic component using Bit:

§ bit add AssociativeTests.Ts —-id math/tests/associativity
§ bit add -c bit.envs/compilers/typescript
§ bit tag math/tests/associativity

3. Push your component to your collection in bit.dev:
(learn how to create your Bit collection here)

§ bit export collection.name math/tests/associativity

3. Now we can easily reuse our shared reusable test component:

import add from ’./add.js’;
import { validateAssociativity } from ’@bit/collection.name.math.tests.associativity’;
test(‘add associativity’, () => {const sample = [1, 5, 3, 1000, 0.3, -17];
// or maybe.. math/samples/realNumbers ..?
validateAssociativity(add, sample);}

Pretty neat! 🤯

Will pngSnapshot components be the next hot thing? 🔮
Could we have checklists like Accessibility support, FPS quota, and screen responsiveness?
What do you think? 😍

Learn More

--

--