npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

zhl-api-test

v1.1.17

Published

Test API

Readme

功能

  • [x] 尽量简单,不要有复杂的依赖;
  • [x] HTTP API 测试;
  • [x] 因为 API 之间有依赖关系,所以要能按照依赖关系进行执行;
  • [x] 某些 API 需要从新的 Session 开始执行,需要满足可以设定 API 测试线从新的 Session 执行;
  • [x] API 可以多次循环测试;
  • [x] API 线可以多次循环测试;
  • [ ] 方便的开启 debug 功能;

使用方法

作者习惯使用 Spring Boot 搭建服务,故使用 Spring Boot 进行说明。

假设 Spring Boot 项目已经搭建好,现在需要开始 API 测试,目录结构如下:

spring-boot-project
    |---- ...
    |---- ...
    |---- ...

导入框架

打开 CMD,cd 到 spring-boot-project 工程目录下。

新建 node 项目,如 api-test

创建目录 api-test

mkdir api-test
cd api-test

npm 初始化 node 项目

npm init

然后根据提示填写信息,不清楚的话就一路回车。

添加依赖库

打开 api-test 目录下 package.json 文件,修改如下:

{
  "name": "api-test",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC",
  // 添加这几行
  "dependencies": {
    "zhl-api-test": "^1.1.12"
  }
}

安装依赖

npm i

等待执行完成。

导入框架完成

此时目录结构:

spring-boot-project
    |---- ...
    |---- ...
    |---- ...
    |---- api-test
         |---- node_modules
              |---- ...
              |---- ...
              |---- ...
         |---- package.json
         |---- package-lock.json
    |---- ...

添加测试单元

在 api-test 目录中,添加 index.js 文件。文件内容如下:

const {StartApiTest} = require('zhl-api-test')

// 正确的请求测试
const TEST_CORRECT_REQUEST = {
    'Hello 测试': {
        dependentItem: [],
        requestConfig: {
            method: 'post',
            url: '/say/hello'
        },
        assert: (data) => {
            console.assert(null != data, "返回数据不为空");
        },
    },
}

// 错误的请求测试
const TEST_WRONG_REQUEST = {
}

const TEST_LIST = Object.assign({}, TEST_CORRECT_REQUEST, TEST_WRONG_REQUEST);

// 进行测试
StartApiTest(TEST_LIST);

注:也可在 api-test 中建目录,分模块进行测试。

创建自动化测试流程

在 Spring Boot 中,一般是在 src/test 目录中的 ...ApplicationTests 类中定义测试。

package ...;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.web.server.LocalServerPort;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;

@SpringBootTest(classes = Application.class,
        webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
class ApplicationTests {

    public static final String NODE_ACTUATOR =
            "node 可执行文件位置,Windows 中为 node.exe,Linux 中为 node";
    private static final List<String> outputString = new CopyOnWriteArrayList<>();
    @LocalServerPort
    private int port;

    private void printMessage(final InputStream input) {
        new Thread(() -> {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8));
            String line;
            try {
                while ((line = bufferedReader.readLine()) != null) {
                    outputString.add(line);
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Test
    void apiTest() {
        System.out.println("port = " + port);
        try {
            File workDir = new File("./api-test");
            Process process = Runtime.getRuntime().exec(NODE_ACTUATOR + " index.js http://127.0.0.1:" + port, null, workDir);

            printMessage(process.getErrorStream());
            printMessage(process.getInputStream());
            int exitVal = process.waitFor();

            System.out.println("\n\n===============================================================================\n\n");
            System.out.println("node test finished, Exit code is " + exitVal);
            AtomicBoolean noErrorMessage = new AtomicBoolean(true);
            outputString.forEach(line -> {
                if (null == line) {
                    return;
                }
                String lineLowerCase = line.trim().toLowerCase();
                if (lineLowerCase.contains("error:")
                        || lineLowerCase.contains("assertion failed")) {
                    System.err.println(line);
                    if (noErrorMessage.get()) {
                        noErrorMessage.set(false);
                    }
                } else {
                    System.out.println(line);
                }
            });
            assert exitVal == 0;
            assert noErrorMessage.get();

            System.out.println("\n\n===============================================================================\n\n");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

此时,便完成了自动化测试。

测试单元规则详解

单个测试:

// 这是一条测试
const TestNodeTemplate = {
    // String,测试名称,可为空
    // 在测试组中作为键值定义即可
    name: '',
    
    // Array<String>,必须定义,所依赖的测试,比如,登录前,需要先请求得到验证码,那么登录测试就得依赖于请求验证码测试
    dependentItem: [],
    
    // Object or Function,必须定义,可以是静态的 Object,也可以定义函数,注:如是函数,则每次请求都会调用函数
    // requestConfig 具体内容参照 AxiosRequestConfig,必须要定义的有 url,method
    requestConfig: {},
    
    // Function or Array<Function>,可为空,数据通过函数或函数数组提供
    // 如果是函数数组,则 cycle 值会被设置为 data.length
    data: () => {
    },
    data: [() => {}, () => {}, () => {}, ....]
    
    // 'parallel' or 'serial',定义测试是并行执行还是串行执行。默认 'serial'
    parallelOrSerial: 'serial',
    
    // Function,必须定义,对返回内容数据的断言函数
    assert: (data, requestConfig) => {
        // 如:
        console.assert(null != data);
    },
    
    // Function,对返回数据的断言函数,默认空函数
    assertResponse: (response, requestConfig) => {
        // 如:
        console.assert(null != response);
    },
    
    // Integer,测试自身循环次数,默认为 1
    // 如果 data 是函数数组,则 cycle 值会被设置为 cycle × data.length
    cycles: 1,
    
    // 循环结束函数。默认为一个返回 true 的函数,
    // 如果该设置了该函数,则 cycle 的值会被重新设置,如果 data 是函数,则 cycle 设置为 1,如果是 函数数组,则设置为 data.length
    // 注:并行执行不支持 requestConfig 函数,且 response 的结果不可预期
    // 有些测试需要判断数据是否符合一定条件,才会结束循环
    loopEndFunction: (response, requestConfig) => {
        return true;
    }
    
    // 延迟执行,默认为 0,不延迟
    delay: 0,
    
    // Integer,测试线循环次数,默认为 1
    // 该定义必须定义在没有任何测试依赖的测试上(即测试线最末尾的测试)
    lineCycles: 1,
    
    // true or false,是否需要在新 Session 中进行该测试,默认 false
    newSession: false,
    
    // Function,回调函数,可做一些请求后的处理工作,默认空函数
    callback: (response, requestConfig) => {
    }
}

测试线:

const TEST_CORRECT_REQUEST = {
    // 定义单个测试,测试名作为键值
    'Hello 测试': {
        dependentItem: [],
        requestConfig: {
            method: 'post',
            url: '/say/hello'
        },
        assert: (data) => {
            console.assert(null != data, "返回数据不为空");
        },
    },
}

依赖规则

假设有如下测试依赖:

A 依赖 B
B 依赖 C、D、E
C 依赖 F
D、F 依赖 G
E 依赖 H

如图示:
A--->B--->C--->F--->G
     |--->D---------↑ 
     |
     |--->E-------->H

则有两条测试线,分别是以 G 和 H 开始的测试线,测试单元执行过程如下:

// 即 B 一定先于 A 执行,(C、F)组合与 D 可以并行执行,无执行顺序要求(这里展示的是串行执行过程,所以只是其中一种情况),
// 但 C、D 一定先于 B 执行,F 一定先于 C 执行,当然,最初执行肯定是 G
G --> D --> F --> C --> B --> A

// H 的这条线只需跟着依赖走即可
H --> E --> B --> A

原则就是:测试单元的依赖一定被全部执行完成后才会执行测试单元本身。