如何在 SharePoint SPFx + react 项目中配置 jest 单元测试框架
Posted
技术标签:
【中文标题】如何在 SharePoint SPFx + react 项目中配置 jest 单元测试框架【英文标题】:how to configure jest unit test framework in SharePoint SPFx + react project 【发布时间】:2021-12-24 16:50:15 【问题描述】:我尝试配置 jest 单元测试框架以响应 SharePoint SPFx 项目
下面我提到所有与 jest 配置和测试用例相关的代码以及
在 react + gulp 中为 SharePoint SPFx 配置项目
package.json
"name": "pnp-sp-crud",
"version": "0.0.1",
"private": true,
"main": "lib/index.js",
"engines":
"node": ">=0.10.0"
,
"scripts":
"build": "gulp bundle",
"clean": "gulp clean",
"test": "jest"
,
"dependencies":
"@microsoft/sp-core-library": "1.10.0",
"@microsoft/sp-lodash-subset": "1.10.0",
"@microsoft/sp-office-ui-fabric-core": "1.10.0",
"@microsoft/sp-property-pane": "1.10.0",
"@microsoft/sp-webpart-base": "1.10.0",
"@pnp/common": "^2.0.3",
"@pnp/sp": "^2.0.3",
"@testing-library/jest-dom": "^5.15.0",
"@testing-library/react": "^12.1.2",
"@types/es6-promise": "0.0.33",
"@types/react": "16.8.8",
"@types/react-dom": "16.8.3",
"@types/webpack-env": "1.13.1",
"jest-cli": "^27.3.1",
"office-ui-fabric-react": "6.189.2",
"react": "^16.8.5",
"react-block-ui": "^1.3.3",
"react-dom": "^16.8.5"
,
"resolutions":
"@types/react": "16.8.8"
,
"devDependencies":
"@microsoft/rush-stack-compiler-3.3": "0.3.5",
"@microsoft/sp-build-web": "1.10.0",
"@microsoft/sp-module-interfaces": "1.10.0",
"@microsoft/sp-tslint-rules": "1.10.0",
"@microsoft/sp-webpart-workbench": "1.10.0",
"@types/chai": "3.4.34",
"@types/jest": "^27.0.2",
"@types/mocha": "2.2.38",
"ajv": "~5.2.2",
"gulp": "~3.9.1",
"jest": "^27.3.1",
"ts-jest": "^27.0.7",
"typescript": "^4.4.4"
jest.config.js
module.exports =
transform:
"^.+\\.(ts|tsx)$": "ts-jest",
,
coveragePathIgnorePatterns: [
"/node_modules/"
],
testRegex: [
'(/__tests__/.*|(\\.|/)(test|spec))\\.[jt]sx?$'
]
;
tsconfig.json
"extends": "./node_modules/@microsoft/rush-stack-compiler-3.3/includes/tsconfig-web.json",
"compilerOptions":
"target": "es5",
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"jsx": "react",
"declaration": true,
"sourceMap": true,
"experimentalDecorators": true,
"skipLibCheck": true,
"outDir": "lib",
"inlineSources": false,
"strictNullChecks": false,
"noUnusedLocals": false,
"typeRoots": [
"./node_modules/@types",
"./node_modules/@microsoft"
],
"types": [
"es6-promise",
"webpack-env",
"jest"
],
"lib": [
"es5",
"dom",
"es2015.collection"
]
,
"include": [
"src/**/*.ts"
],
"exclude": [
"node_modules",
"lib"
]
PnpSpCrud.tsx
import * as React from 'react';
import styles from './PnpSpCrud.module.scss';
import IPnpSpCrudProps from './IPnpSpCrudProps';
import escape from '@microsoft/sp-lodash-subset';
require("@pnp/logging");
require("@pnp/common");
require("@pnp/odata");
import sp from "@pnp/sp/presets/all";
import BlockUi from 'react-block-ui';
export default class PnpSpCrud extends React.Component<IPnpSpCrudProps,
name: any,
tech: any,
employees: any[],
isUpdate: boolean,
empId: any,
empIndex: any,
blocking: boolean
>
constructor(props)
super(props);
this.state =
name: '',
tech: '',
employees: [],
isUpdate: false,
empId: '',
empIndex: '',
blocking: false
;
this.getEmployee();
this.enterName = this.enterName.bind(this);
this.enterTechnology = this.enterTechnology.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
private enterName(event)
this.setState( name: event.target.value );
private enterTechnology(event)
this.setState( tech: event.target.value );
private handleSubmit(event)
this.addEmployee(this.state);
event.preventDefault();
public render(): React.ReactElement<IPnpSpCrudProps>
return (
<BlockUi tag="div" blocking=this.state.blocking>
<div className=styles.pnpSpCrud>
<div className=styles.container>
<div className=styles.row>
<div className=styles.column>
/* <span className=styles.title>Welcome to SharePoint!</span>
<p className=styles.subTitle>Customize SharePoint experiences using Web Parts.</p>
<p className=styles.description>escape(this.props.description)</p> */
<h4>Add Employee</h4>
<form>
<div className="row">
<div className="col-md-6">
<div className="form-group">
<label>Employee Name</label>
<input type="text" className="form-control" placeholder="Enter name" value=this.state.name onChange=this.enterName />
</div>
</div>
<div className="col-md-6">
<div className="form-group">
<label>Technology</label>
<input type="text" className="form-control" placeholder="Enter Technology" value=this.state.tech onChange=this.enterTechnology />
</div>
</div>
/* <input type="text" placeholder="Employee Name" value=this.state.name onChange=this.enterName></input>
<input type="text" style= marginLeft: 5 placeholder="Technology" value=this.state.tech onChange=this.enterTechnology></input>
<div style= marginTop: 5 >
<input type="submit" value="Submit" />
</div> */
</div>
</form>
<button className="btn btn-primary mb-5" onClick=this.handleSubmit>Submit</button>
<h4>Employees</h4>
<table className="table table-dark">
<tbody>
<tr>
<th>#</th>
<th>Name</th>
<th colSpan=2>Technology</th>
</tr>
this.state.employees.map((ele, index) =>
return (
<tr>
<td>index + 1</td>
<td>ele.Title</td>
<td>ele.tech</td>
<td style= color: 'while', cursor: 'pointer' ><i className="fa fa-pencil" aria-hidden="true" onClick=() => this.updateEmployee(ele.ID, index)></i></td>
<td style= color: 'while', cursor: 'pointer' ><i className="fa fa-trash" style= color: 'while' aria-hidden="true" onClick=() => this.deleteEmployee(ele.ID, index)></i></td>
</tr>
);
)
</tbody>
</table>
/* <a href="https://aka.ms/spfx" className=styles.button>
<span className=styles.label>Learn more</span>
</a> */
</div>
</div>
</div>
</div>
</BlockUi>
);
private getEmployee()
return new Promise((resolve, reject) =>
sp.web.lists.getByTitle('EmployeeList').items.select('ID, Title, tech').get().then((result: any[]) =>
console.log("get employees result", result);
resolve(result);
this.setState(
employees: result
);
).catch((err) =>
console.log("error in get employees", err);
);
);
private addEmployee(state)
if (this.state.name && this.state.tech)
if (this.state.isUpdate)
this.setState( blocking: true );
return new Promise((resolve, reject) =>
let data =
Title: state.name,
tech: state.tech
;
sp.web.lists.getByTitle('EmployeeList').items.getById(this.state.empId).update(data).then((result: any) =>
resolve(result);
alert('Employee Updated');
this.state.employees[this.state.empIndex].Title = data.Title;
this.state.employees[this.state.empIndex].tech = data.tech;
this.setState( name: '', tech: '', isUpdate: false, empId: '', empIndex: '', blocking: false );
).catch((err) =>
alert('Employee Not Updated');
this.setState( blocking: false );
console.log("error in update employee", err);
);
);
else
this.setState( blocking: true );
return new Promise((resolve, reject) =>
let data =
Title: state.name,
tech: state.tech
;
sp.web.lists.getByTitle('EmployeeList').items.add(data).then((result: any) =>
console.log("result from add employee", result);
resolve(result);
alert('Employee added');
this.state.employees.push(
ID: result.data.ID,
Title: state.name,
tech: state.tech
);
this.setState( name: '', tech: '', blocking: false );
).catch((err) =>
alert('Employee not added');
this.setState( blocking: false );
console.log("error in add employee", err);
);
);
else
alert("Enter Name and Technology");
private updateEmployee(id, index)
this.setState( blocking: true );
return new Promise((resolve, reject) =>
sp.web.lists.getByTitle('EmployeeList').items.getById(id).get().then((result: any) =>
console.log("get employee by id result", result);
resolve(result);
this.setState(
name: result.Title,
tech: result.tech,
isUpdate: true,
empId: id,
empIndex: index,
blocking: false
);
).catch((err) =>
this.setState( blocking: false );
console.log("error in get employee by id", err);
);
);
private deleteEmployee(id, index)
this.setState( blocking: true );
return new Promise((resolve, reject) =>
sp.web.lists.getByTitle('EmployeeList').items.getById(id).delete().then((result: any) =>
console.log("delete employee by id result", result);
resolve(result);
alert("Employee deleted");
this.state.employees.splice(index, 1);
this.setState(
empId: '',
empIndex: '',
blocking: false
);
).catch((err) =>
this.setState( blocking: false );
console.log("error in delete employee by id", err);
);
);
PnpSpCrud.spec.tsx
import PnpSpCrud from '../PnpSpCrud';
test('renders learn react link', () =>
expect(PnpSpCrud).toBeTruthy();
);
Error while run the unit test
【问题讨论】:
【参考方案1】:据我所知,SPFx 目前尚未正式支持 jest(或任何其他单元测试框架)。
我遇到了类似的问题,请查看 GitHub 上的这个帖子,讨论如何让 jest 与 SPFx 和 PNP 一起工作: https://github.com/pnp/pnpjs/issues/1425
这是带有工作配置的演示仓库: https://github.com/nbelyh/spfx-unit-testing-pnpjs
【讨论】:
感谢@nikolay 的回复。我找到了另一种解决方案,您也可以尝试在 SPFx 项目中配置 jest:github.com/Voitanos/jest-preset-spfx-react16 上面帖子中提到过,请查看与它相关的这些cmets:github.com/pnp/pnpjs/issues/1425#issuecomment-721110960 知道了。您能否也看看另一个相同的问题:***.com/questions/69973511/… 请阅读上面链接的评论,它似乎解决了您面临的问题。 我看到了你的 GitHub 问题。但是对于Import PNP没有任何解决方案。我还查看了您的存储库:spfx-unit-testing-pnpjs。但就我而言,我需要对 SharePoint 列表和 SPFx 组件使用导入。谢谢以上是关于如何在 SharePoint SPFx + react 项目中配置 jest 单元测试框架的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 SharePoint SPFX webpart 构建团队应用程序
如何在 SharePoint SPFx + react 项目中配置 jest 单元测试框架
使用 SPFx 扩展将自定义操作部署到 SharePoint 2019 列表