介绍

test-util插件模块是为了解决编写大量重复的测试代码而封装的。使用该插件模块后,编写测试代码的开发者只需关心输入输出。而且方便后期可以解析代码文件导出测试用例文档。
这一块其实没什么好讲,基本的使用方法请见测试文档,核心代码看看就好

核心源码

  1. import { IMidwayApplication } from '@midwayjs/core';
  2. import { createHttpRequest } from '@midwayjs/mock';
  3. /**
  4. * 请求方式枚举
  5. */
  6. export enum HTTP_METHOD {
  7. POST = 'post',
  8. GET = 'get',
  9. PUT = 'put',
  10. DELETE = 'delete'
  11. }
  12. /**
  13. * 验证规则枚举
  14. */
  15. export enum EXCEPT_RULE {
  16. TO_BE = 'toBe', //相等
  17. TO_CONTAIN = 'toContain' //包含
  18. }
  19. //固定的期望结果
  20. export interface FixedExpect {
  21. type: 'fixedExpect';
  22. //固定的期望结果
  23. label?: string; //期望结果描述
  24. key: string; //result.data.code //结果使用键值字符串 格式:result.*.*
  25. not?: boolean; //结果是否取反
  26. rule: EXCEPT_RULE; //判断规则
  27. value: any; //期望值
  28. groups?: string[]; //分组
  29. }
  30. /**
  31. * 判断是否有实现FixedExpect接口
  32. * @param object
  33. * @returns
  34. */
  35. function instanceOfFixedExpect(object: any): object is FixedExpect {
  36. return object.type === 'fixedExpect';
  37. }
  38. //自定义期待结果
  39. export interface CustomExpect {
  40. type: 'customExpect';
  41. label?: string; //期望结果描述
  42. groups?: string[]; //分组
  43. callback: (result: any) => void;
  44. }
  45. /**
  46. * 校验是否实现CustomExpect接口
  47. * @param object
  48. * @returns
  49. */
  50. function instanceOfCustomExpect(object: any): object is CustomExpect {
  51. return object.type === 'customExpect';
  52. }
  53. /**
  54. * 测试模型子项
  55. */
  56. export interface TestModelItem {
  57. name: string;
  58. path: string;
  59. method: HTTP_METHOD;
  60. param?: { [prop: string]: any };
  61. body?: { [prop: string]: any };
  62. query?: { [prop: string]: any };
  63. expectArray: Array<FixedExpect | CustomExpect>;
  64. }
  65. /**
  66. * 测试用例模型
  67. */
  68. export interface TestModel {
  69. name: string;
  70. array: TestModelItem[];
  71. }
  72. /**
  73. * 方法接口
  74. */
  75. export interface Method {
  76. name: string;
  77. args:
  78. | {
  79. [prop: string]: any;
  80. }
  81. | any;
  82. }
  83. /**
  84. * 解析key,获取深度对象值
  85. * @param obj
  86. * @param key
  87. * @returns
  88. */
  89. const deepObject = (obj: object, key: string) => {
  90. if (typeof obj !== 'object') {
  91. return obj;
  92. }
  93. let value = obj;
  94. const keys = key.split('.');
  95. keys.forEach((item) => {
  96. value = obj[item];
  97. });
  98. return value;
  99. };
  100. /**
  101. * 求数组交集
  102. * @param a
  103. * @param b
  104. * @returns
  105. */
  106. const intersection = (a: any[], b: any[]): any[] => {
  107. const s = new Set(b);
  108. return [...new Set(a)].filter((x) => s.has(x));
  109. };
  110. const chainingCall = (target, methods, index = 0) => {
  111. if (index < methods.length) {
  112. const method = methods[index];
  113. const func = deepObject(target, method.name);
  114. if (func instanceof Function) {
  115. target = func.call(target, method.args);
  116. return chainingCall(target, methods, ++index);
  117. } else {
  118. throw new Error(`${target}不存在函数:${method.name}`);
  119. }
  120. } else {
  121. return target;
  122. }
  123. };
  124. export interface ExecuteOption {
  125. groups?: string[];
  126. beforeAll: () => Promise<IMidwayApplication>;
  127. afterAll: (app: IMidwayApplication) => Promise<void>;
  128. }
  129. const sleep = (time: number) => {
  130. return new Promise((resolve) => {
  131. setTimeout(() => resolve(true), time);
  132. });
  133. };
  134. /**
  135. * 执行测试
  136. * @param model
  137. */
  138. export const executeTest = (model: TestModel, executeOption: ExecuteOption) => {
  139. describe(model.name, () => {
  140. let app: IMidwayApplication;
  141. beforeAll(async () => {
  142. app = await executeOption.beforeAll();
  143. await sleep(2000); //延时2秒 避免服务启动的时候有东西未加载完毕
  144. });
  145. afterAll(async () => {
  146. await executeOption.afterAll(app);
  147. });
  148. /**
  149. * 测试方法
  150. * @param item
  151. */
  152. const testFunc = async (item: TestModelItem, option?: ExecuteOption) => {
  153. it(item.name, async () => {
  154. //1、将param参数替换进path
  155. if (item.param) {
  156. for (const key in item.param) {
  157. const regexp = new RegExp(':' + key, 'g');
  158. item.path = item.path.replace(
  159. regexp,
  160. typeof item.param[key] === 'object'
  161. ? item.param[key].toString()
  162. : item.param[key]
  163. );
  164. }
  165. }
  166. const executeGroups = option && option.groups ? option.groups : null;
  167. //2、添加链式方法数组
  168. const methods = new Array<Method>();
  169. methods.push({ name: item.method, args: item.path });
  170. //确认是否需要调用query函数
  171. !!item.query && methods.push({ name: 'query', args: item.query });
  172. //确认是否需要调用send函数
  173. !!item.body && methods.push({ name: 'send', args: item.body });
  174. //3、链式调用
  175. const result = await chainingCall(createHttpRequest(app), methods);
  176. for (const expectItem of item.expectArray) {
  177. if (
  178. executeGroups &&
  179. expectItem.groups &&
  180. intersection(executeGroups, expectItem.groups).length === 0
  181. ) {
  182. continue;
  183. }
  184. if (instanceOfFixedExpect(expectItem)) {
  185. const methods = new Array<Method>();
  186. methods.push({
  187. name: (expectItem.not ? 'not.' : '') + expectItem.rule,
  188. args: expectItem.value
  189. });
  190. chainingCall(expect(deepObject(result, expectItem.key)), methods);
  191. } else if (instanceOfCustomExpect(expectItem)) {
  192. expectItem.callback(result);
  193. }
  194. }
  195. });
  196. };
  197. for (const item of model.array) {
  198. testFunc(item, executeOption);
  199. }
  200. });
  201. };