问题描述
背景
我最近了解了clasp,并对使用TDD在本地编辑我的Google Apps Scripts(GAS)的可能性感到兴奋。
注意:也许可以使用现有的GAS编辑器编写测试,但是我更愿意使用现代的编辑器:)
clasp可以很好地工作,但是我无法弄清楚如何模拟单元测试的依赖关系(尽管我很高兴使用任何有效的工具,但主要通过jest来>
- 通过使用gas-local软件包,我走得最远,并且能够模拟测试中的单个依赖项
- 但是我找不到在单个测试/调用中模拟多个依赖项的方法,因此我创建了this issue
挑战
- 我是一位JavaScript新手,在研究此问题时可能错过了一些重要的事情:)
- 尽管安装了@types/google-apps-script,但我不清楚如何分别使用ES5或ES2015语法来“要求”或“导入” Google Apps脚本模块。有关此示例,请参见下文。
相关的StackOverflow帖子
尽管在单元测试here中也有类似的SO问题,但大多数内容/评论似乎都来自拍前时代,在跟进其余线索时,我无法找到解决方案。 (当然,我未经训练的眼睛很可能错过了一些东西!)。
尝试
使用本地气体
如上所述,在尝试使用gas-local来模拟多个依赖项之后,我创建了一个问题(请参阅上面的链接)。我的配置与我在下面描述的jest.mock
测试相似,但值得注意以下差异:
- 我在
gas-local
测试中使用了ES5语法 - 我的包裹配置可能略有不同
使用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"
}
}
解决方法
注意:您的问题范围很广,可能需要澄清。
clasp效果很好,但是我无法弄清楚如何模拟单元测试的依赖关系(主要是通过玩笑,尽管我很高兴使用任何有效的工具)
您不需要Jest或任何特定的测试框架即可模拟全局Apps脚本对象。
// 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,但我不清楚如何使用ES5或ES2015语法“要求”或“导入” Google Apps脚本模块
@types/google-apps-script
不包含模块,并且您不导入它们。这些是TypeScript declaration files。您的编辑器(如果支持TypeScript)将在后台读取这些文件,并且即使是普通的JavaScript文件,您也可以突然获得自动完成功能。
其他评论
- 在这里检查函数是否返回字符串,也许只是为了使您的示例非常简单。但是,必须强调的是,此类测试最好留给TypeScript。
- 自从您返回HTML字符串以来,我有义务指出Apps脚本出色的HTML Service和模板功能。
- 单元测试还是集成测试?您提到了单元测试,但是依赖全局变量通常表示您可能不是单元测试。考虑重构您的函数,以便它们接收对象作为输入,而不是从全局范围调用它们。
- 模块语法:如果使用
export default foo
,则在导入时不使用花括号:import foo from "foo.js"
,但是如果使用export function foo() {
,则使用花括号:import { foo } from "foo.js"