【问题标题】:How to mock dependencies when testing GAS with CLASP使用 CLASP 测试 GAS 时如何模拟依赖项
【发布时间】:2020-12-05 06:50:12
【问题描述】:

背景

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

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

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

  • 我使用gas-local 包走得最远,并且能够在测试中模拟单个依赖项
    • 但是我找不到在单个测试/调用中模拟多个依赖项的方法,因此我创建了this issue

挑战

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

相关 StackOverflow 帖子

虽然在单元测试here 上存在类似的 SO 问题,但大部分内容/cmets 似乎都来自 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"],
};

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"
  }
}

【问题讨论】:

  • TheMaster:太酷了!只要像@dwmorrin 建议的方法那样工作,那么我可能会很好地在控制台中获得我的结果。但如果我想在浏览器中查看结果,我会记住gunitgs2 :)

标签: unit-testing google-apps-script jestjs tdd clasp


【解决方案1】:

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

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

您不需要 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 的 TypeScript 文件。你可以省略它。 您只需对上面的 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 declaration files。您的编辑器(如果它支持 TypeScript)会在后台读取这些文件,并且突然之间您将能够获得自动完成功能,即使是在纯 JavaScript 文件中也是如此。

其他 cmets

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

【讨论】:

  • 哇。感谢@dwmorrin 的详细回复!老实说,我对您建议的更改的相对简单性(和有效性)感到震惊。我能够成功运行我的测试!回复:您的观点:(对不起,降价失败)-为什么有必要将 getSheetByName 依赖项嵌套在 getActiveSpreadsheet 的模拟(?)中? - 回复:HTML:当时我开始使用 SheetConverter 来帮助我在电子邮件中显示工作表内容 - 单元测试与集成测试:我希望使用更本地化的范围
  • 内联ss 变量,你有SpreadsheetApp.getActiveSpreadsheet().getSheetByName()。点语法要求左侧表达式为对象,而括号要求左侧表达式为函数,因此.getSheetByName() 必须既是函数又是对象getActiveSpreadsheet() 返回的属性。这些都是纯 JavaScript 规则,与 GAS 或 Jest 无关。深入了解 JavaScript 将为您阐明这些问题。
  • 有没有办法从原始js文件自动创建global.SpreadsheetApp = { getActiveSpreadsheet: () =&gt; ({ getSheetByName: () =&gt; ({}), }), };
  • @TheMaster - 从 .js 文件创建以进行测试?我的第一个担心是你在那个时候降低了测试的价值。可以想象,您可以编写一个程序来解析 GAS 的 .d.ts 文件并为 Jest 生成一些虚拟代码,但这种自动生成的价值再次值得怀疑。如果您手动编写它们,您可以继续将它们添加到 .js 文件中,并根据需要将该文件导入到您的测试文件中。手动编写函数所需的预期全局 API 本身可以被视为有价值的文档,尤其是在 API 应该更改时。
  • @dwmorrin - 我想我明白你在说什么。我使用 JavaScript 的大部分经验都是通过 TypeScript 完成的,所以我可能会尽可能尝试将我的 GAS 和测试转换为 use TypeScript。 @TheMaster - 你问:Is there a way to create mocks (?) automatically from original js files?。我相信这就是gas-local 试图实现的目标。但即使我能够让它完全工作,我也喜欢能够为每个模拟手动定义一个类型。所以我得试试ts-auto-mock。谢谢!
猜你喜欢
  • 2023-03-13
  • 1970-01-01
  • 1970-01-01
  • 2020-12-06
  • 2015-08-25
  • 2011-07-20
  • 1970-01-01
  • 2012-07-11
  • 2017-03-22
相关资源
最近更新 更多