使用AVA测试你的Typescript

919 阅读2分钟

这是我参与11月更文挑战的第13天,活动详情查看:2021最后一次更文挑战

介绍

AVA是一个轻量级测试工具,不仅api简单,还能并发执行测试,没有隐式的全局变量,对ts lib类的项目测试特别友好。

安装

在项目中运行

npm init ava

这个命令将会在项目中安装ava,并修改package.json

{
	"scripts": {
		"test": "ava"
	},
	"devDependencies": {
		"ava": "^3.15.0"
	}
}

编写ts

整体项目结构如下

├───scripts/
│   └───build.js
├───src/
│   └───index.ts
├───test/
│   └───ava.ts
├───CHANGELOG.md
├───package-lock.json
├───package.json
├───README.md
├───tsconfig.json
└───yarn.lock

假设我们编写了一个工具函数:

src/index.ts

// {k1:[k2,value]},k1查k2,k2查value
type Dictionary<T> = {
    [key in string | number]: [number, T] | T;
  };
  
  type Entries<T> = Array<[number, T]>;
  type EntriesFunction<T> = () => Entries<T>;
  /**
   * 对enum的扩展,可以根据k1找到k2,根据k2找到value
   * @param data {k1:[k2,value]},可以简化为{k1:value},此时k2为k1在属性中的index值
   * @returns
   */
  
  export default function Enum<P extends Dictionary<T>, T>(data: P) {
    type U = P extends Dictionary<infer S> ? S : 'string'; //令U=S,S类型为T
    const map = new Map<string | number, number | U>();
    const entries: Entries<U> = [];
    if (!data) {
      return data;
    }
    // Object.getOwnPropertyNames是有序的
    Object.getOwnPropertyNames(data).forEach((key1, index) => {
      let key2, value: U;
      if (Array.isArray(data[key1])) {
        let arr = data[key1] as [number, U];
        key2 = arr[0];
        value = arr[1];
      } else {
        key2 = index;
        value = data[key1] as U;
      }
      map.set(key1, key2);
      map.set(key2, value);
      entries.push([key2, value]);
    });
    return new Proxy(
      {} as { [key in keyof Exclude<P, 'entries'>]: number } & {
        [key: number]: U;
        entries: EntriesFunction<U>;
      },
      {
        get: function (_target, propKey: string) {
          if (propKey === 'entries') {
            return () => entries;
          }
  
          let value = map.get(propKey) ?? data[propKey];
          if (value === undefined && /\d+/.test(propKey)) {
            value = map.get(Number(propKey)) || data[Number(propKey)];
          }
          return value;
        },
      },
    );
  }

这个函数的功能是提供一个便于访问的字典,期望的功能和输出如下:

const AuditStatus = Enum({
  UNAUDIT: [1, '未审核'],
  AUDITING: [2, '审核中'],
  AUDITED: [3, '审核通过'],
  REFUSED: [4, '审核拒绝'],
});
// AuditStatus.UNAUDIT => 1
// AuditStatus[1] => '未审核'
const FileSource = Enum({
  local:  '本地',
  remote: '远端',
});
// FileSource.local = 0
// FileSource[0] = '本地'
// FileSource.entries() => [[0,'本地'],[1,'远端']]

配置

在设计中,这个函数函数是pure ESM,需要进行一些配置:

tsconfig.json

{

	"compilerOptions": {
		"outDir": "dist",
		"target": "es6",
		"module": "ESNext",
		"moduleResolution": "node",
		"downlevelIteration": true,
		"alwaysStrict": true,
		"sourceMap": true,
		"declaration": true,
		"removeComments": false,
		"strict": true,
		"noImplicitReturns": true,
		"noUnusedLocals": true,
		"noImplicitAny": false,
		"noImplicitThis": false,
		"experimentalDecorators": true,
		"useDefineForClassFields": true,
		"esModuleInterop": true,
		"rootDir": "src"
	},
	"exclude": [
		"node_modules","test","dist"
	]
}

package.json

"ava": {
    "extensions": {
      "ts": "module"
    },
    "nonSemVerExperiments": {
      "configurableModuleFormat": true
    },
    "nodeArguments": [
      "--loader=ts-node/esm",
      "--experimental-specifier-resolution=node"
    ]
  }

接下来编写测试用例

编写测试

test/ava.ts

import test from "ava";
import Enum from "../src";
const AuditStatus = Enum({
  UNAUDIT: [1, "未审核"],
  REFUSED: [4, "审核拒绝"],
});
const FileSource = Enum({
  LOCAL: "本地",
  REMOTE: "远端",
});
console.log(AuditStatus, FileSource);

test("entries", (t) => {
  t.like(
    { e: FileSource.entries() },
    {
      e: [
        [0, "本地"],
        [1, "远端"],
      ],
    }
  );
  t.like(
    { e: AuditStatus.entries() },
    {
      e: [
        [1, "未审核"],
        [4, "审核拒绝"],
      ],
    }
  );
});

test("k1 to k2", async (t) => {
  t.is(AuditStatus.UNAUDIT, 1);
  t.is(AuditStatus.REFUSED, 4);
  t.is(FileSource.LOCAL, 0);
  t.is(FileSource.REMOTE, 1);
});
test("k2 to value", async (t) => {
  t.is(AuditStatus[1], "未审核");
  t.is(AuditStatus[4], "审核拒绝");
  t.is(FileSource[0], "本地");
  t.is(FileSource[1], "远端");
});

运行 npm run test

如果通过:

image.png

如果有失败,比如

test("k2 to value", async (t) => {
  t.is(AuditStatus[1], "未审核");
  t.is(AuditStatus[4], "审核拒绝");
  t.is(FileSource[0], "本地");
  t.is(FileSource[1], "远");
});

ava会输出详细的信息,能显示输入值和期待值的差异,这也是ava的一大特性

image.png