- The Modern Javascript Bootcamp Course (2022)
- The most up-to-date JS resource online! Master Javascript by building a beautiful portfolio of projects!
- Tutorial for tme (Building a Testing Framework From Scratch)
- Colt Steele
- Stephen Grider
- Command to search for
.test.jsfrom any directory.
tme- Must be a Node-based CLI framework
- Must be able to test browser-based JS apps
- Must require very, very little setup
- Must be able to test a whole application, not just one little widget
- CLI must have a 'watch mode' so we don't have to keep restarting it over and over
- CLI must automatically find and run all files in our project that have a name of '*.test.js'
| Mocha in the Browser | |
|---|---|
| createAutoComplete | Email Validation |
| We had a function to run as manay times as we wanted | Form logic executes instantly with 'index.js' being loaded |
| We could create a new autocomplete for each test | No easy ability to bind to a second form |
| Direct handle onto the function we want to test | Effectively, can only run one test |
- File Collection
- Find all files ending in '*test.js' recursively through a folder
- Store a reference to each file we find
- After getting a full list of the rest files, execute them one by one
- Test environment setup
- Test file execution
- Report results
Alireza: Adding async await to it function definition
At 4:10 we add async and await keywords to the function definition of global.it so that node waits for DOM to fully load before testing it. This seems to break the reporting if our tests do not require asynchronous operation - for example if we run the tme command on the sampleproject folder with forEach.test.js and forEachCopy.test.js.
See the report below:
Before Adding async - await
---- test/forEach.test.js
OK - should sum an array
OK - beforeEach is ran each time
---- test/forEachCopy.test.js
OK - should sum an arrays
OK - beforeEach is ran each timesAfter adding async await
---- test/forEach.test.js
---- test/forEachCopy.test.js
OK - should sum an array
OK - beforeEach is ran each time
OK - should sum an arrays
OK - beforeEach is ran each timesIs there a way to rectify the reporting issue?
Stephen: Great catch, I did not see this previously. We could fix this by storing a reference to each 'it' function then invoking them all in a row. Fixed code:
async runTests() {
for (let file of this.testFiles) {
console.log(chalk.gray(`---- ${file.shortName}`));
const beforeEaches = [];
const its = [];
global.render = render;
global.beforeEach = fn => {
beforeEaches.push(fn);
};
global.it = async (desc, fn) => {
its.push({ desc, fn });
};
try {
require(file.name);
for (let _it of its) {
const { desc, fn } = _it;
for (let _before of beforeEaches) {
_before();
}
try {
await fn();
console.log(chalk.green(`\tOK - ${desc}`));
} catch (err) {
const message = err.message.replace(/\n/g, '\n\t\t');
console.log(chalk.red(`\tX - ${desc}`));
console.log(chalk.red('\t', message));
}
}
} catch (err) {
console.log(chalk.red(err));
}
}
}Alireza: Nice one - It's quite a change then to fix this issue. So, we need to implement a queuing system with arrays to make sure tests and console logs are executed in order. One last question - Does
global.itfunction definition need to beasync?
global.it = async (desc, fn) => {
its.push({ desc, fn });
};I don't see any await keywords within its definition now.
Stephen: You're correct, the 'async' keyword could be dropped with that rewrite Yes, the system gets a bit more complicated by adding in the 'it' queue. However - trust me - this is still a lot simpler than what other testing libraries like Mocha do :)
Alireza: Awesome! Oh yes this is by far way better than Jasmine and Mocha! I think I will try to re-write tme in Python too for my data science applications! I'll post a link to my repo when I do. Thanks for showing us how to create this framework - this is super useful.
One thing I still haven't understood from these testing libs are concepts like spies and hooks, etc. Do you think you can create/extend the course in the future to implement our own spies and hooks? Amazing course and project section again! Thanks a lot Stephen.
Stephen: Spy implementations can be really straightforward, here's a sample:
module.exports = class Spy {
constructor(target, key) {
this.target = target;
this.key = key;
this.calls = [];
this._func = target[key];
target[key] = (...args) => this.exec(...args);
}
exec(args) {
this.calls.push(args);
}
reset() {
this.target[this.key] = this._func;
}
};You can modify the 'exec' function to also 'call through' to the original method. This one just stores the provided arguments.
To use this in the TME project, add in the following to the runner.js file:
const Spy = require('./spy');
global.Spy = Spy;
That assumes you created the spy in a 'spy.js' file.Finally, to use it in a test:
const assert = require('assert');
class Counter {
constructor() {
this.count = 0;
}
add(value) {
for (let i = 0; i < value; i++) {
this.incrementByOne();
}
}
incrementByOne() {
this.count++;
}
}
it('can add many values at once', () => {
const counter = new Counter();
const spy = new Spy(counter, 'incrementByOne');
counter.add(10);
assert.equal(spy.calls.length, 10);
});
