6

背景

我最近了解了CLASP ,并对使用TDD在本地编辑我的Google Apps 脚本(GAS)的可能性感到兴奋。

注意:可能有一种方法可以使用现有的 GAS 编辑器编写测试,但如果可能的话,我更喜欢使用现代编辑器

clasp 效果很好,但我不知道如何模拟单元测试的依赖项(主要通过jest,尽管我很高兴使用任何有效的工具)

  • 我通过使用gas-local包走得最远,并且能够在测试中模拟单个依赖 项
    • 但是我找不到在单个测试/调用中模拟多个依赖项的方法,所以我创建了这个问题

挑战

尽管安装了@types/google-apps-script,但我不清楚如何分别使用 ES5 或 ES2015 语法“要求”或“导入”Google Apps 脚本模块——请参阅下面的说明。

相关 StackOverflow 帖子

尽管这里有一个关于单元测试的类似 SO 问题但大多数内容/评论似乎来自 pre-clasp 时代,我在跟进剩余线索时无法找到解决方案。(当然,我未经训练的眼睛很可能错过了什么!)。

尝试

使用气体局部

正如我上面提到的,在使用 gas-local 时尝试模拟多个依赖项后,我创建了一个问题(参见上面的链接)。我的配置类似于jest.mock我在下面描述的测试,但值得注意的是以下差异:

  • 我使用 ES5 语法进行gas-local测试
  • 我的包配置可能略有不同

使用 jest.mock

LedgerScripts.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;

结果(运行jest命令后)

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)

作为参考,如果我转到google-apps-script.spreadsheet.d.ts具有所需类型的文件,我会在文件顶部看到以下声明...

declare namespace GoogleAppsScript {
  namespace Spreadsheet {

...和文件底部的这个:

declare var SpreadsheetApp: GoogleAppsScript.Spreadsheet.SpreadsheetApp;

所以也许我只是导入SpreadsheetApp不正确?

其它文件

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"],
};

包.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"
  }
}
4

1 回答 1

6

注意:您的问题范围很广,可能需要澄清。

扣子效果很好,但我不知道如何模拟单元测试的依赖项(主要是通过开玩笑,尽管我很高兴使用任何有效的工具)

您不需要 Jest 或任何特定的测试框架来模拟全局 Apps Script 对象。

// LedgerScripts.test.js
import getSummaryHTML from "./LedgerScripts.js";

global.SpreadsheetApp = {
  getActiveSpreadsheet: () => ({
    getSheetByName: () => ({}),
  }),
};

console.log(typeof getSummaryHTML() === "string");
$ node LedgerScripts.test.js
true

所以也许我只是错误地导入了 SpreadsheetApp?

.d.ts是的,导入Jest是不正确的。Jest 不需要用于SpreadsheetApp. 你可以省略它。您只需对上面的 Jest 示例稍作修改。

// LedgerScripts.test.js - Jest version
import getSummaryHTML from "./LedgerScripts";

global.SpreadsheetApp = {
  getActiveSpreadsheet: () => ({
    getSheetByName: () => ({}),
  }),
};

test("summary returns a string", () => {
  expect(typeof getSummaryHTML()).toBe("string");
});

尽管安装了@types/google-apps-script,但我不清楚如何“要求”或“导入”Google Apps 脚本模块,无论是使用 ES5 还是 ES2015 语法

@types/google-apps-script不包含模块,您不导入它们。这些是TypeScript 声明文件。您的编辑器(如果它支持 TypeScript)将在后台读取这些文件,并且突然之间您将能够获得自动完成功能,即使是在纯 JavaScript 文件中也是如此。

附加评论

  • 在这里,您检查一个函数是否返回一个字符串,也许只是为了使您的示例非常简单。但是,必须强调的是,最好将此类测试留给 TypeScript。
  • 由于您返回了一个 HTML 字符串,我觉得有义务指出 Apps Script 出色的HTML 服务和模板能力。
  • 单元测试还是集成测试?您提到了单元测试,但依赖全局变量通常表明您可能不是单元测试。考虑重构您的函数,以便它们接收对象作为输入,而不是从全局范围调用它们。
  • 模块语法:如果使用export default foo,则导入时不使用大括号:import foo from "foo.js"但如果使用export function foo() {,则使用大括号:import { foo } from "foo.js"
于 2020-08-15T20:31:32.453 回答