Tiny Test Runner

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.gs file and run testHelpers from 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!

NitroGAS Chrome Extension

Want this snippet handy inside the Apps Script editor? NitroGAS gives you free themes and snippets — plus optional Co-Pilot when you want a boost (1-week, 1-month, or yearly passes — no subscription). Happy Coding!

Get the Extension