I recently learned about CLASP and became excited about the possibility of using TDD to edit my Google Apps Scripts (GAS) locally.
NOTE: there might be a way to write tests using the existing GAS editor, but I'd prefer to use a modern editor if at all possible
clasp works great, but I cannot figure out how to mock dependencies for unit tests (primarily via jest, though I'm happy to use any tool that works)
Despite installing @types/google-apps-script, I am unclear on how to "require" or "import" Google Apps Script modules whether using ES5 or ES2015 syntax, respectively--see below for an illustration of this.
Although there is a similar SO question on unit testing here, most of the content/comments appear to be from the pre-clasp era, and I was unable to arrive at a solution while following up the remaining leads. (Granted, it's very possible my untrained eye missed something!).
As I mentioned above, I created an issue (see link above) after trying to mock multiple dependencies while using gas-local. My configuration was similar to the jest.mock
test I describe below, though it's worth noting the following differences:
gas-local
testsLedgerScripts.test.js
import { getSummaryHTML } from "./LedgerScripts.js";
import { SpreadsheetApp } from '../node_modules/@types/google-apps-script/google-apps-script.spreadsheet';
test('test a thing', () => {
jest.mock('SpreadSheetApp', () => {
return jest.fn().mockImplementation(() => { // Works and lets you check for constructor calls
return { getActiveSpreadsheet: () => {} };
});
});
SpreadsheetApp.mockResolvedValue('TestSpreadSheetName');
const result = getSummaryHTML;
expect(result).toBeInstanceOf(String);
});
LedgerScripts.js
//Generates the summary of transactions for embedding in email
function getSummaryHTML(){
var ss = SpreadsheetApp.getActiveSpreadsheet();
var dashboard = ss.getSheetByName("Dashboard");
// Do other stuff
return "<p>some HTML would go here</p>"
}
export default getSummaryHTML;
Result (after running jest
command)
Cannot find module '../node_modules/@types/google-apps-script/google-apps-script.spreadsheet' from 'src/LedgerScripts.test.js'
1 | import { getSummaryHTML } from "./LedgerScripts.js";
> 2 | import { SpreadsheetApp } from '../node_modules/@types/google-apps-script/google-apps-script.spreadsheet';
| ^
3 |
4 | test('test a thing', () => {
5 | jest.mock('SpreadSheetApp', () => {
at Resolver.resolveModule (node_modules/jest-resolve/build/index.js:307:11)
at Object.<anonymous> (src/LedgerScripts.test.js:2:1)
For reference, if I go to the google-apps-script.spreadsheet.d.ts
file that has the types I want, I see the following declarations at the top of the file...
declare namespace GoogleAppsScript {
namespace Spreadsheet {
...and this one at the bottom of the file:
declare var SpreadsheetApp: GoogleAppsScript.Spreadsheet.SpreadsheetApp;
So maybe I am just importing SpreadsheetApp
incorrectly?
jest.config.js
module.exports = {
clearMocks: true,
moduleFileExtensions: [
"js",
"json",
"jsx",
"ts",
"tsx",
"node"
],
testEnvironment: "node",
};
babel.config.js
module.exports = {
presets: ["@babel/preset-env"],
};
package.json
{
"name": "ledger-scripts",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"author": "",
"license": "ISC",
"dependencies": {
"@babel/core": "^7.11.1",
"@babel/preset-env": "^7.11.0",
"@types/google-apps-script": "^1.0.14",
"@types/node": "^14.0.27",
"babel-jest": "^26.3.0",
"commonjs": "0.0.1",
"eslint": "^7.6.0",
"eslint-plugin-jest": "^23.20.0",
"gas-local": "^1.3.1",
"requirejs": "^2.3.6"
},
"devDependencies": {
"@types/jasmine": "^3.5.12",
"@types/jest": "^26.0.9",
"jest": "^26.3.0"
}
}
Note: the scope of your question is broad and may require clarification.
clasp works great, but I cannot figure out how to mock dependencies for unit tests (primarily via jest, though I'm happy to use any tool that works)
You don't need Jest or any particular testing framework to mock the global Apps Script objects.
// LedgerScripts.test.js
import getSummaryHTML from "./LedgerScripts.js";
global.SpreadsheetApp = {
getActiveSpreadsheet: () => ({
getSheetByName: () => ({}),
}),
};
console.log(typeof getSummaryHTML() === "string");
$ node LedgerScripts.test.js
true
So maybe I am just importing SpreadsheetApp incorrectly?
Yes, it is incorrect to import .d.ts
into Jest.
Jest doesn't need the TypeScript file for SpreadsheetApp
. You can omit it.
You only need to slightly modify the above example for Jest.
// LedgerScripts.test.js - Jest version
import getSummaryHTML from "./LedgerScripts";
global.SpreadsheetApp = {
getActiveSpreadsheet: () => ({
getSheetByName: () => ({}),
}),
};
test("summary returns a string", () => {
expect(typeof getSummaryHTML()).toBe("string");
});
Despite installing @types/google-apps-script, I am unclear on how to "require" or "import" Google Apps Script modules whether using ES5 or ES2015 syntax
@types/google-apps-script
does not contain modules and you do not import them. These are TypeScript declaration files. Your editor, if it supports TypeScript, will read those files in the background and suddenly you'll have the ability to get autocomplete, even in plain JavaScript files.
export default foo
, you then import without curly braces: import foo from "foo.js"
but if you use export function foo() {
then you use the curly braces: import { foo } from "foo.js"