はじめに
弊社では、Googleスプレッドシートに Google Apps Script を組み合わせて使うことがあります。
最近になって、自分も Google Apps Script を使う機会ができたので、使い方を覚えることにします。
以下のことができるようにします。
- clasp を使用する。
- TypeScript を使用する。
- テストコードを Jest で書く。
手順
初期プロジェクトの作成
clasp をインストールする
npm init -y
npm install @google/clasp
Google Apps Script API を有効にすることも忘れずに。
Googleスプレッドシートと Apps Script を作成する
npx clasp create \
--type sheets \
--title sample20230812-01 \
--rootDir .
--title
で指定した名前の Googleスプレッドシートが、マイドライブに作成されます。
.claspignore を作成する
echo "node_modules/**
coverage/**
jest.config.ts
**/*.test.ts
" > .claspignore
.claspignore
ファイルを作成する場合、node_modules/**
を含めておかないと、clasp push
がえんえんと実行中になり終わらない...
eslint をインストールする
npm install --save-dev @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint typescript
echo "/* eslint-env node */
module.exports = {
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
root: true,
};" > .eslintrc.cjs
Goolge Apps Script の TypeScript 用の設定を追加する
npm i -S @types/google-apps-script
echo '{
"compilerOptions": {
"lib": ["esnext"],
"experimentalDecorators": true
}
}' > tsconfig.json
Jest をインストールする
npm install --save-dev jest
npm install --save-dev ts-jest
npm install --save-dev @jest/globals
npm install --save-dev @types/jest
npm install --save-dev ts-node
npx jest --init
The following questions will help Jest to create a suitable configuration for your project
✔ Would you like to use Jest when running "test" script in "package.json"? … yes
✔ Would you like to use Typescript for the configuration file? … yes
✔ Choose the test environment that will be used for testing › node
✔ Do you want Jest to add coverage reports? … yes
✔ Which provider should be used to instrument code for coverage? › v8
✔ Automatically clear mock calls, instances, contexts and results before every test? … yes
✏ Modified /(プロジェクトのパス)/package.json
📝 Configuration file created at /(プロジェクトのパス)/jest.config.ts
「TypeScript Deep Dive」のサイトに記載のある設定を追加します。
なお、設定ファイルが jest.config.js
という名前で作成されますが、ファイル内に記述されている import type {Config} from 'jest';
という書き方が TypeScript 用の書き方に見えるので、ファイル名を jest.config.ts
に変えてみます。
mv jest.config.ts jest.config.ts.org
cat jest.config.ts.org | sed -e 's/^};$/ roots: [\n "<rootDir>"\n ],\n testMatch: [\n "**\/__tests__\/**\/*.+(ts|tsx|js)",\n "**\/?(*.)+(spec|test).+(ts|tsx|js)"\n ],\n transform: {\n "^.+\\\\.(ts|tsx)$": "ts-jest"\n },\n};/' > jest.config.ts
# rm jest.config.ts.org
js.config.ts に追加される内容は以下。
roots: [
"<rootDir>"
],
testMatch: [
"**/__tests__/**/*.+(ts|tsx|js)",
"**/?(*.)+(spec|test).+(ts|tsx|js)"
],
transform: {
"^.+\\.(ts|tsx)$": "ts-jest"
},
ソースコードとテストコードを作成する
ソースコード
たとえば clasp の GitHub で紹介されているデモ用のソースコードの場合。
Google Apps Script で定義されている Logger.log()
が使用されており、テストを実行するために少々工夫します。
function hello(): void {
Logger.log("Hello, Apps Script!");
}
module.exports = hello;
確認用に、関数の戻り値の型を書いてみました。
TypeScript 構文が読み取られる設定が正しく行われていなければ、:void
の部分で構文エラーになります。そうなったら、何かを間違えているはず。エラーがなくなるように頑張るしかない (-_-;
また、テストコードから関数を参照できるようにするために、module.exports
を追加しました。
テストコード
import { describe, expect, test } from '@jest/globals';
const hello = require('../src/hello');
// Logger をモックするためのクラスを定義する
class LoggerMock implements GoogleAppsScript.Base.Logger {
private lastLog: string;
clear (): void { this.lastLog = undefined; }
getLog (): string { return this.lastLog; }
log (data: string): LoggerMock { this.lastLog = data; return this; }
}
describe('テストです', ()=> {
beforeEach(() => {
// Logger にインスタンスをセットする
global.Logger = new LoggerMock();
});
test('ログ出力に成功すること', () => {
hello();
expect(global.Logger.getLog()).toBe('Hello, Apps Script!');
});
});
Google Apps Script で Logger
という変数が定義されていますが、テストの実行時は値が undefined になっているので、代わりのオブジェクトを代入します。
global 名前空間の変数の値を書き換えるとは、なんてことをしてくれるんだ、というアンチパターンなのかもしれませんが、そもそも値が undefined なのだから、まぁいいか...
テストを実行する
npm run test
PASS tests/hello.test.ts
テストです ✓ ログ出力に成功すること (2 ms)
リモートにプッシュして実行する
npx clasp login
npx clasp push
プッシュに成功したら、Google Apps Script のエディタ画面で実行してみて、スクリプトの実行が成功するかを確認します。
おわりに
Google Apps Script を TypeScript で書きつつ、テストコードの実行ができました。