I want to test with Google Apps Script too! (Clasp + Typescript + Jest)
I have published a library in Google Apps Script (hereafter, GAS). When developing the library, I chose a tech stack of Clasp + Typescript + Jest
to shorten the test feedback cycle. I would like to share my development experience. I haven't done anything particularly unusual.
How do you test Google Apps Script?
Isn't it hard to debug by accessing script.google.com?
- It's slow because you're stepping over the network
- It's troublesome to adjust the service side (like preparing data) when integrating with G Suite services
- The debug function is poor
It's very stressful. It's fine for a simple GAS, but if you want to create a slightly complex GAS, you'll feel it's a problem.
Let's run it locally
Google has released a command-line tool called Clasp that allows you to run GAS in a local environment.
https://github.com/google/clasp
Also, since Clasp supports Typescript, it is now possible to code focusing on types.
https://www.npmjs.com/package/@types/google-apps-script
Choosing Typescript makes interface design easier. Of course, I think the same can be achieved with .gs
files.
Next, by combining with a testing tool called Jest, testing is possible in a local environment.
https://jestjs.io/docs/getting-started
However, you can't just write test code. For example, when coding a test to get a calendar event, suppose you write the following script.
const calendar: Calendar = CalendarApp.getCalendarById(
"<your google calendar id>"
);
calendar
.getEvents(new Date("2020-01-01"), new Date("2020-01-02"))
.forEach((calendarEvent: CalendarEvent) => {
console.log(calendarEvent.getTitle());
});
If you write it like this, it will actually go to get the real calendar event. In a test, you would want to avoid such processing.
Therefore, to replace CalendarApp
with a fake object, or a Mock object, we apply the principle of dependency inversion.
interface ICalendarApp {
calendars?: Array<ICalendar>;
getCalendarById(id: string): ICalendar;
}
interface ICalendar {
calendarEvents?: Array<ICalendarEvent>;
getEvents(startTime: Date, endTime: Date): Array<ICalendarEvent>;
}
interface ICalendarEvent {
title?: string;
getTitle(): string;
}
class CalendarAppMock implements ICalendarApp {
calendars?: Array<ICalendar>;
getCalendarById(id: string): ICalendar {
return this.calendars![0].calendar;
}
}
class CalendarAppImpl implements ICalendarApp {
getCalendarById(id: string): ICalendar {
const calendar: ICalendar = CalendarApp.getCalendarById(id);
return calendar;
}
}
Prepare such an interface/class and modify the previous code as follows.
const calendar: ICalendar = new CalendarAppMock().getCalendarById();
calendar
.getEvents(new Date("2020-01-01"), new Date("2020-01-02"))
.forEach((calendarEvent: ICalendarEvent) => {
console.log(calendarEvent.getTitle());
});
As a result, you can now insert a Mock object instead of CalendarApp
. This makes local testing possible.
Of course, in product code, you should use CalendarAppImpl
instead of CalendarAppMock
.
If the number of objects to be replaced with Mock increases, you might want to consider a DI container like InversifyJS.
https://github.com/inversify/InversifyJS
By doing this, tests by Jest will work.
In fact, I was able to fully test the library I developed and published.
https://www.npmjs.com/package/@silverbirder/caat
CaAT $ npm run test -- --coverage
> jest "--coverage"
PASS __tests__/utils/dateUtils.test.ts
PASS __tests__/group/groupImpl.test.ts
PASS __tests__/member/memberImpl.test.ts
---------------------|---------|----------|---------|---------|-------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
---------------------|---------|----------|---------|---------|-------------------
All files | 98.43 | 97.62 | 96.67 | 98.37 |
__tests__ | 100 | 100 | 100 | 100 |
generator.ts | 100 | 100 | 100 | 100 |
src/calendar | 93.1 | 100 | 92.31 | 92.59 |
calendarAppImpl.ts | 60 | 100 | 50 | 60 | 6,7
calendarAppMock.ts | 100 | 100 | 100 | 100 |
src/group | 100 | 100 | 100 | 100 |
groupImpl.ts | 100 | 100 | 100 | 100 |
src/member | 100 | 94.74 | 100 | 100 |
memberImpl.ts | 100 | 94.74 | 100 | 100 | 38
src/utils | 100 | 100 | 100 | 100 |
dateUtils.ts | 100 | 100 | 100 | 100 |
---------------------|---------|----------|---------|---------|-------------------
Test Suites: 3 passed, 3 total
Tests: 23 passed, 23 total
Snapshots: 0 total
Time: 2.826s, estimated 6s
Ran all test suites.
The test of the function provided as a library is finished in just about 3 seconds. I was able to develop locally stress-free.
For more details, please see the source code of the library I actually created (__tests__).
Conclusion
GAS is very convenient. It improves productivity. You can quickly build APIs, and integration with G Suite is (of course) easy.
However, if the code becomes low in maintainability, it will become obsolete and no one will be able to take care of it. Test code is essential to always stay clean. Those who operate GAS, please consider test code.
Eh, wait a minute. Introduction of the library!
This is a library that I would like teams who are doing agile development and managing schedules with Google Calendar to use.
https://github.com/silverbirder/caat
CaAT is the Google Apps Script Library that Calculate the Assigned Time in Google Calendar.
What you can do with this tool is as follows.
- Get the time (minutes) scheduled in a specific user's Google Calendar for a specified period
- Overlapping appointments are considered as continuous appointments
- Specified time/words are considered as non-calculable (such as lunch)
- Who is taking a day off and when, get from all-day events
There is actually sample code, so please refer to it.
Share
Related tags
- Created a GAS Library, zoom-meeting-creator, to Automatically Generate Zoom Meetings
- Cotlin is a Tool for Collecting Links on Twitter, Discover Presentations from Around the World
- Why the Combination of FetchAll and RedirectURL in Google Apps Script is Bad
- I tried creating rMinc, a service that registers GMail to GCalendar
- Trying Out container-structure-test on a Docker Image
- Integration Testing for Next.js and DB using Testcontainers
- Trying Million Lint
- Trying the Playwright Component Test
- I want to do Document Testing with Typescript
- How to Mock tRPC Communication on Storybook with MSW
- E2E Testing with Cucumber and Screenplay Design
- Comprehensive Testing Pattern Guide for Web Frontend
- Mockable unit testing methodology completed only with BigQuery
- Trying ArchUnit with Typescript
- Investigated and Summarized Testing Perspectives for Web Apps (25 Selections)
- Things to Keep in Mind When Writing Unit Tests
- Continuously Monitor Visual Regression Tests with CircleCI + BackstopJS (Puppeteer)