单元测试检查加载项的功能,而无需网络或服务连接,包括与 Office 应用程序的连接。 单元测试服务器端代码和 不 调用 Office JavaScript API 的客户端代码在 Office 外接程序中与在任何 Web 应用程序中相同,因此不需要特殊文档。 但调用 Office JavaScript API 的客户端代码难以测试。 为了解决这些问题,我们创建了一个库来简化单元测试中的模拟 Office 对象的创建: Office-Addin-Mock。 该库通过以下方式简化了测试:
- Office JavaScript API 必须在 Office 应用程序 ((如 Excel、PowerPoint 或 Word) )的上下文中的 Webview 控件中初始化,因此它们无法在开发计算机上运行单元测试的过程中加载。 可以将 Office-Addin-Mock 库导入到测试文件中,这样就可以在运行测试的 Node.js 进程中模拟 Office JavaScript API。
-
特定于应用程序的 API 具有负载和同步方法,必须按相对于其他函数的特定顺序调用这些方法。 此外,您必须使用某些参数调用
load方法,具体取决于 稍后 将在要测试的函数中由代码读取的 Office 对象的属性。 但是,单元测试框架本质上是无状态的,因此它们无法记录是否load调用 或sync被调用或传递了哪些参数。load使用 Office-Addin-Mock 库创建的模拟对象具有跟踪这些内容的内部状态。 此内部状态使模拟对象能够模拟实际 Office 对象的错误行为。 例如,如果正在测试的函数尝试读取未首先传递给load的属性,则该测试将返回类似于 Office 返回的错误。
库不依赖于 Office JavaScript API,你可以将其用于任何 JavaScript 单元测试框架,例如:
本文中的示例使用 Jest 框架。 有关使用 Mocha 框架的示例,请参阅 Office-Addin-Mock 主页。
先决条件
本文假定你熟悉单元测试和模拟的基本概念,包括如何创建和运行测试文件,并且具有一些单元测试框架的经验。
提示
如果要使用 Microsoft Visual Studio (VS) ,建议阅读在 Visual Studio 中对 JavaScript 和 TypeScript 进行单元测试 一文,了解有关 VS 中的 JavaScript 单元测试的一些基本信息,然后返回本文。
安装工具
若要安装库,请打开命令提示符,导航到外接程序项目的根目录,然后输入以下命令。
npm install office-addin-mock --save-dev
基本用法
项目将具有一个或多个测试文件。 (请参阅以下示例中的测试框架和示例测试文件的说明。) 使用
require或import关键字 (keyword) 导入具有调用 Office JavaScript API 的函数测试的任何测试文件的库,如以下示例所示。// CommonJS const OfficeAddinMock = require("office-addin-mock");// ES6 import OfficeAddinMock from "office-addin-mock";导入包含要使用
require或import关键字 (keyword) 测试的外接程序函数的模块。 以下示例假定测试文件位于包含加载项代码文件的 文件夹的子文件夹中。// CommonJS const myOfficeAddinFeature = require("../my-office-add-in");// ES6 import myOfficeAddinFeature from "../my-office-add-in";创建一个数据对象,该对象具有测试函数时需要模拟的属性和子属性。 以下示例演示模拟 Excel Workbook.range.address 属性和 Workbook.getSelectedRange 方法的对象。 此对象不是最终的模拟对象。 将其视为用于创建最终模拟对象的
OfficeMockObject种子对象。const mockData = { workbook: { range: { address: "C2:G3", }, getSelectedRange: function () { return this.range; }, }, };将数据对象传递给
OfficeMockObject构造函数。 有关返回OfficeMockObject的对象,请注意以下事项。- 它是 OfficeExtension.ClientRequestContext 对象的简化模拟。
- mock 对象具有数据对象的所有成员,并且还具有 和
sync方法的load模拟实现。 - 模拟对象模拟对象的关键错误行为
ClientRequestContext。 例如,如果正在测试的 Office API 尝试在不首先加载属性并调用sync的情况下读取属性,则测试失败并显示类似于生产运行时中引发的错误:“错误,未加载属性”。
const contextMock = new OfficeAddinMock.OfficeMockObject(mockData);注意
有关该
OfficeMockObject类型的完整参考文档位于 Office-Addin-Mock。在测试框架的语法中,添加 函数的测试。 使用
OfficeMockObject对象来代替它模拟的对象,在本例中为ClientRequestContext对象。 下面继续 Jest 中的示例。 此示例测试假定要测试的外接程序函数称为getSelectedRangeAddress,它采用ClientRequestContext对象作为参数,并返回当前所选区域的地址。 本文 稍后将介绍完整示例。test("getSelectedRangeAddress should return the address of the range", async function () { expect(await getSelectedRangeAddress(contextMock)).toBe("C2:G3"); });根据测试框架和开发工具的文档运行测试。 通常,有一个 package.json 文件,其中包含用于执行测试框架的脚本。 例如,如果 Jest 是框架, package.json 将包含以下项:
"scripts": { "test": "jest", -- Other scripts omitted. -- }若要运行测试,请在项目的根目录中的命令提示符中输入以下内容。
npm test
示例
本节中的示例使用 Jest 及其默认设置。 这些设置支持 CommonJS 模块。 有关如何将 Jest 和 Node.js 配置为使用 TypeScript 和支持 ECMAScript 模块的信息,请参阅有关 入门 和 ECMAScript 模块的 Jest 文档。
若要运行这些示例中的任何一个,请执行以下步骤。
- 为相应的 Office 主机应用程序创建 Office 外接程序项目, (例如 Excel 或 Word) 。 快速执行此作的一种方法是将 Yeoman 生成器用于 Office 加载项。
- 在项目的根目录中, 安装 Jest。
- 安装 office-addin-mock 工具。
- 创建与示例中第一个文件完全相同的文件,并将其添加到包含项目的其他源文件(通常称为
\src)的文件夹。 - 创建源文件文件夹的子文件夹,并为其指定适当的名称,例如
\tests。 - 创建与示例中的测试文件完全相同的文件,并将其添加到子文件夹中。
-
test将脚本添加到 package.json 文件,然后运行测试,如基本用法中所述。
模拟 Office 通用 API
此示例假定任何支持 Office 常用 API ((例如 Excel、PowerPoint 或 Word) )的主机的 Office 加载项。 加载项在名为 my-common-api-add-in-feature.js的 文件中具有其功能之一。 以下代码显示了 文件的内容。 函数addHelloWorldText将文本“Hello World!”设置为文档中当前选择的任何内容;例如,Word中的区域、Excel 中的单元格或 PowerPoint 中的文本框。
const myCommonAPIAddinFeature = {
addHelloWorldText: async () => {
const options = { coercionType: Office.CoercionType.Text };
await Office.context.document.setSelectedDataAsync("Hello World!", options);
}
}
module.exports = myCommonAPIAddinFeature;
名为 的测试 my-common-api-add-in-feature.test.js文件位于子文件夹中,相对于外接程序代码文件的位置。 以下代码显示了 文件的内容。 顶级属性是 context( Office.Context 对象),因此要模拟的对象是此属性的父级: Office 对象。 关于此代码,请注意以下几点:
- 构造
OfficeMockObject函数不会将所有 Office 枚举类添加到 mockOffice对象,因此必须在种子对象中显式添加CoercionType.Text外接程序方法中引用的值。 - 由于未在节点进程中加载 Office JavaScript 库,因此必须声明并初始化
Office外接程序代码中引用的对象。
const OfficeAddinMock = require("office-addin-mock");
const myCommonAPIAddinFeature = require("../my-common-api-add-in-feature");
// Create the seed mock object.
const mockData = {
context: {
document: {
setSelectedDataAsync: function (data, options) {
this.data = data;
this.options = options;
},
},
},
// Mock the Office.CoercionType enum.
CoercionType: {
Text: {},
},
};
// Create the final mock object from the seed object.
const officeMock = new OfficeAddinMock.OfficeMockObject(mockData);
// Create the Office object that is called in the addHelloWorldText function.
global.Office = officeMock;
/* Code that calls the test framework goes below this line. */
// Jest test
test("Text of selection in document should be set to 'Hello World'", async function () {
await myCommonAPIAddinFeature.addHelloWorldText();
expect(officeMock.context.document.data).toBe("Hello World!");
});
模拟 Outlook API
虽然 Outlook API 是通用 API 模型的一部分,但它们具有围绕 Mailbox 对象构建的特殊体系结构。 我们为 Outlook 提供了一个独特的示例。 此示例假定 Outlook 外接程序在名为 my-outlook-add-in-feature.js的 文件中具有其功能之一。 以下代码显示了 文件的内容。 函数addHelloWorldText将文本“Hello World!”设置为邮件撰写窗口中当前选择的任何内容。
const myOutlookAddinFeature = {
addHelloWorldText: async () => {
Office.context.mailbox.item.setSelectedDataAsync("Hello World!");
}
}
module.exports = myOutlookAddinFeature;
名为 my-outlook-add-in-feature.test.js的测试文件位于相对于外接程序代码文件位置的子文件夹中。 以下代码显示了 文件的内容。 顶级属性是 context,一个 Office.Context 对象。 模拟目标的对象是此属性的父对象: Office 对象。 请注意有关此代码的以下详细信息。
-
host模拟库在内部使用模拟对象上的 属性来标识 Office 应用程序。 这是 Outlook 的必需项。 它目前对任何其他 Office 应用程序没有任何用途。 - 由于未在节点进程中加载 Office JavaScript 库,因此必须声明并初始化
Office外接程序代码中引用的对象。
const OfficeAddinMock = require("office-addin-mock");
const myOutlookAddinFeature = require("../my-outlook-add-in-feature");
// Create the seed mock object.
const mockData = {
// Identify the host to the mock library (required for Outlook).
host: "outlook",
context: {
mailbox: {
item: {
setSelectedDataAsync: function (data) {
this.data = data;
},
},
},
},
};
// Create the final mock object from the seed object.
const officeMock = new OfficeAddinMock.OfficeMockObject(mockData);
// Create the Office object that is called in the addHelloWorldText function.
global.Office = officeMock;
/* Code that calls the test framework goes below this line. */
// Jest test
test("Text of selection in message should be set to 'Hello World'", async function () {
await myOutlookAddinFeature.addHelloWorldText();
expect(officeMock.context.mailbox.item.data).toBe("Hello World!");
});
模拟特定于 Office 应用程序的 API
测试使用特定于应用程序的 API 的函数时,模拟正确的对象类型。 你有两个选项。
模拟 OfficeExtension.ClientRequestObject。 如果要测试的函数满足以下两个条件,请使用此选项。
- 它不调用 Host。
run函数,例如 Excel.run。 - 它不引用 Host 对象的任何其他直接属性或方法。
- 它不调用 Host。
以下小节显示了这两种类型的测试的示例。
注意
Office-Addin-Mock 库当前不支持模拟集合类型对象。 这些对象是遵循 集合 命名模式的应用程序特定 API 中的所有对象,例如 WorksheetCollection。 我们正在努力将此支持添加到库。
模拟 ClientRequestContext 对象
此示例假定 Excel 加载项在名为 my-excel-add-in-feature.js的文件中具有其功能之一。 以下代码显示了 文件的内容。 请注意, getSelectedRangeAddress 函数是在传递给 的回调中调用的 Excel.run帮助程序方法。
const myExcelAddinFeature = {
getSelectedRangeAddress: async (context) => {
const range = context.workbook.getSelectedRange();
range.load("address");
await context.sync();
return range.address;
}
}
module.exports = myExcelAddinFeature;
名为 my-excel-add-in-feature.test.js的测试文件位于相对于外接程序代码文件位置的子文件夹中。 以下代码显示了 文件的内容。 请注意,顶级属性为 workbook,因此要模拟的对象是 的父 Excel.Workbook级 :a ClientRequestContext 对象。
const OfficeAddinMock = require("office-addin-mock");
const myExcelAddinFeature = require("../my-excel-add-in-feature");
// Create the seed mock object.
const mockData = {
workbook: {
range: {
address: "C2:G3",
},
// Mock the Workbook.getSelectedRange method.
getSelectedRange: function () {
return this.range;
},
},
};
// Create the final mock object from the seed object.
const contextMock = new OfficeAddinMock.OfficeMockObject(mockData);
/* Code that calls the test framework goes below this line. */
// Jest test
test("getSelectedRangeAddress should return address of selected range", async function () {
expect(await myOfficeAddinFeature.getSelectedRangeAddress(contextMock)).toBe("C2:G3");
});
模拟主机对象
本示例假定Word外接程序在名为 my-word-add-in-feature.js的 文件中具有其功能之一。 以下代码显示了 文件的内容。
const myWordAddinFeature = {
insertBlueParagraph: async () => {
return Word.run(async (context) => {
// Insert a paragraph at the end of the document.
const paragraph = context.document.body.insertParagraph("Hello World", Word.InsertLocation.end);
// Change the font color to blue.
paragraph.font.color = "blue";
await context.sync();
});
}
}
module.exports = myWordAddinFeature;
名为 my-word-add-in-feature.test.js的测试文件位于相对于外接程序代码文件位置的子文件夹中。 以下代码显示了 文件的内容。 请注意,顶级属性是 context,一个 ClientRequestContext 对象,因此要模拟的对象是此属性的父对象:对象 Word 。 请注意有关此代码的以下详细信息。
- 构造
OfficeMockObject函数创建最终的模拟对象时,可确保子ClientRequestContext对象具有sync和load方法。 - 构造
OfficeMockObject函数不会向 mockWord对象添加run函数,因此必须在种子对象中显式添加函数。 - 构造
OfficeMockObject函数不会将所有 Word 枚举类添加到 mockWord对象,因此必须在种子对象中显式添加InsertLocation.end外接程序方法中引用的值。 - 由于未在节点进程中加载 Office JavaScript 库,因此必须声明并初始化
Word外接程序代码中引用的对象。
const OfficeAddinMock = require("office-addin-mock");
const myWordAddinFeature = require("../my-word-add-in-feature");
// Create the seed mock object.
const mockData = {
context: {
document: {
body: {
paragraph: {
font: {},
},
// Mock the Body.insertParagraph method.
insertParagraph: function (paragraphText, insertLocation) {
this.paragraph.text = paragraphText;
this.paragraph.insertLocation = insertLocation;
return this.paragraph;
},
},
},
},
// Mock the Word.InsertLocation enum.
InsertLocation: {
end: "end",
},
// Mock the Word.run function.
run: async function(callback) {
await callback(this.context);
},
};
// Create the final mock object from the seed object.
const wordMock = new OfficeAddinMock.OfficeMockObject(mockData);
// Define and initialize the Word object that is called in the insertBlueParagraph function.
global.Word = wordMock;
/* Code that calls the test framework goes below this line. */
// Jest test set
describe("Insert blue paragraph at end tests", () => {
test("color of paragraph", async function () {
await myWordAddinFeature.insertBlueParagraph();
expect(wordMock.context.document.body.paragraph.font.color).toBe("blue");
});
test("text of paragraph", async function () {
await myWordAddinFeature.insertBlueParagraph();
expect(wordMock.context.document.body.paragraph.text).toBe("Hello World");
});
})
注意
有关该 OfficeMockObject 类型的完整参考文档位于 Office-Addin-Mock。
另请参阅
- Office-Addin-Mock npm 页面 安装点。
- 开放源代码存储库是 Office-Addin-Mock。
- 开玩笑
- 摩卡
- 茉莉花