Apps Script has no built-in test framework, and you don't need one to catch the obvious stuff. runTests(tests) runs an object of named test functions, logs ✅ or ❌ for each, toasts a summary when there's an active spreadsheet, and throws if anything failed, so a red run is impossible to miss. assertEquals(actual, expected, label) compares with a JSON deep-equal, which handles arrays and plain objects.
When to use
- Testing pure helpers (date math, parsing, diffing) before a trigger depends on them
- Asking Co-Pilot for tests first, then the change
- A quick “did I break it?” check after editing a shared library
How to use this snippet
/**
* Deep-equal assert via JSON. Throws with a readable message on mismatch.
* Note: key order matters and Dates compare as ISO strings.
*/
function assertEquals(actual, expected, label) {
var a = JSON.stringify(actual), e = JSON.stringify(expected);
if (a !== e) {
throw new Error((label ? label + ': ' : '') + 'expected ' + e + ' but got ' + a);
}
}
/**
* Run named tests: { 'adds tax': function () { ... }, ... }.
* Logs each result, toasts a summary in a spreadsheet, throws if any failed.
*
* @param {Object<string, function()>} tests
* @return {{passed: number, failed: number, failures: string[]}}
*/
function runTests(tests) {
var passed = 0, failures = [];
Object.keys(tests).forEach(function (name) {
try {
tests[name]();
passed++;
console.log('✅ ' + name);
} catch (err) {
failures.push(name + ': ' + (err && err.message ? err.message : err));
console.log('❌ ' + name + ': ' + (err && err.message ? err.message : err));
}
});
var summary = passed + ' passed, ' + failures.length + ' failed';
console.log(summary);
try {
var ss = SpreadsheetApp.getActiveSpreadsheet();
if (ss) ss.toast(summary, failures.length ? 'Tests failed' : 'Tests passed', 5);
} catch (e) {} // not bound to a sheet, or no UI
if (failures.length) throw new Error(summary + '\n' + failures.join('\n'));
return { passed: passed, failed: 0, failures: [] };
}
Example
function testHelpers() {
runTests({
'normalizes email': function () {
assertEquals(normalizeEmail(' Ana@Example.COM '), 'ana@example.com');
},
'diff finds a changed field': function () {
var d = diffObjectsByKey([{ Id: 1, Status: 'new' }], [{ Id: 1, Status: 'done' }], 'Id');
assertEquals(d.changed[0].fields, { Status: ['new', 'done'] }, 'changed fields');
}
});
}
Tips:
- Keep tests in a separate
Tests.gsfile and runtestHelpersfrom the editor. - Test pure functions. For anything that writes to a sheet, test the function that builds the values, not the write.
- JSON deep-equal is strict about key order. Compare
Object.keys(x).sort()first if order may differ.
Tip: ask NitroGAS Co-Pilot for the tests before the change. Paste the function and say “write runTests cases for this first”.
Happy Coding!
