⛽
asideでGAS開発を快適にする(モックでテストもあるよ)
快適な GAS コーディングをサポートする aside を実際に使ってみました。
初期設定やテスト用モックの準備で何箇所か沼ってしまったので、知見を共有したいと思います。
aside についてくるもの
- clasp (Typescript)
- EditorConfig
- ESLint
- Prettier
- Jest
- 開発環境、本番環境へのデプロイ
便利!
aside のセットアップ
Script API を有効化
clasp のお作法に従い、https://script.google.com/home/usersettings から Google Apps Script API を有効化しておく必要があります。
有効化せず aside を利用した場合、Error: ENOENT: no such file or directory, lstat 'dist/.clasp.json'
と非特異的なエラーが出て沼ります。
aside を実行
npx @google/aside init
clasp 未セットアップ環境での初回実行時には clasp login
が実行されます。
セットアップされるもの
開発環境
- EditorConfig
- ESLint
- Prettier
- Jest
必要に応じて VSCode などの拡張機能を入れるとより幸せです。
便利なスクリプト
package.json
にいくつかのスクリプトが登録されています。
特に、次のコマンドは普段の開発や CI/CD で便利そうです 👀
npm run lint
npm run test
npm run deploy
npm run deploy:prod
モックしてテストする
次の関数のテストを作成してみます 👀
spreadsheet-utils.ts
/**
* シートを取得する。
* 該当のシートがない場合、シートを作成する。
*
* @param name - シートの名称
* @returns 取得または作成したシート
*/
export function getSheet(name: string): GoogleAppsScript.Spreadsheet.Sheet {
const spreadsheet = SpreadsheetApp.getActiveSpreadsheet();
const sheet = spreadsheet.getSheetByName(name);
if (sheet !== null) return sheet;
return spreadsheet.insertSheet().setName(name);
}
SpreadsheetApp
を用意する
グローバルオブジェクト jest.config.json (抜粋)
"globals": {
"SpreadsheetApp": {}
}
spreadsheet、sheet のモックを作成する
GoogleAppsScript.Spreadsheet.Sheet
はインターフェースです。
インターフェースからモックを作成するのに、今回は jest-mock-extended を使用します。
npm install jest-mock-extended
spreadsheet-utils.test.ts (抜粋)
const spreadsheet: MockProxy<GoogleAppsScript.Spreadsheet.Spreadsheet> =
mock<GoogleAppsScript.Spreadsheet.Spreadsheet>();
const sheet: MockProxy<GoogleAppsScript.Spreadsheet.Sheet> =
mock<GoogleAppsScript.Spreadsheet.Sheet>();
呼び出す関数の返り値をモックしていきます。
次に示すのは、「シートが存在する場合」のモックです。
spreadsheet-utils.test.ts (抜粋)
spreadsheet.getSheetByName.mockReturnValue(sheet);
SpreadsheetApp.getActiveSpreadsheet = jest
.fn()
.mockReturnValue(spreadsheet);
呼び出し、評価
spreadsheet-utils.test.ts (抜粋)
const sheetName = '存在するシート';
expect(getSheet(sheetName)).toBe(sheet);
expect(spreadsheet.getSheetByName).toBeCalledWith(sheetName);
コード全体
存在しないシート名を指定した場合を含めた、テストの記述例です。
spreadsheet-utils.test.ts
import { mock, MockProxy } from 'jest-mock-extended';
describe('example-module', () => {
describe('getSheet', () => {
it('存在するシートを指定する', () => {
const spreadsheet: MockProxy<GoogleAppsScript.Spreadsheet.Spreadsheet> =
mock<GoogleAppsScript.Spreadsheet.Spreadsheet>();
const sheet: MockProxy<GoogleAppsScript.Spreadsheet.Sheet> =
mock<GoogleAppsScript.Spreadsheet.Sheet>();
spreadsheet.getSheetByName.mockReturnValue(sheet);
SpreadsheetApp.getActiveSpreadsheet = jest
.fn()
.mockReturnValue(spreadsheet);
const sheetName = '存在するシート';
expect(getSheet(sheetName)).toBe(sheet);
expect(spreadsheet.getSheetByName).toBeCalledWith(sheetName);
});
it('存在しないシートを指定する', () => {
const spreadsheet: MockProxy<GoogleAppsScript.Spreadsheet.Spreadsheet> =
mock<GoogleAppsScript.Spreadsheet.Spreadsheet>();
const sheet: MockProxy<GoogleAppsScript.Spreadsheet.Sheet> =
mock<GoogleAppsScript.Spreadsheet.Sheet>();
spreadsheet.getSheetByName.mockReturnValue(null);
spreadsheet.insertSheet.mockReturnValue(sheet);
sheet.setName.mockReturnValue(sheet);
SpreadsheetApp.getActiveSpreadsheet = jest
.fn()
.mockReturnValue(spreadsheet);
const sheetName = '存在しないシート';
expect(getSheet(sheetName)).toBe(sheet);
expect(spreadsheet.insertSheet).toBeCalledTimes(1);
expect(sheet.setName).toBeCalledWith(sheetName);
});
});
});
Discussion