官方文档, 文档很详细, 内容也比较丰富

jestfacebook出品,是一个完整的js单元测试框架,有比较完善的测试方案,也可以配合其他断言库、 mock库等

mock方式

  1. mock functions
  2. mock module

mock functions

mock函数, 比较灵活, 可以灵活地mock我们想要的预期场景

比如我们要测试redis:

//redis.js
const redis = require('redis');
const EXPIRED = 30 * 24 * 3600;

function createClient() {
    return redis.createClient()
}

let set = function (key, value) {
    return new Promise((resolve, rejected) => {
        let client = createClient();
        client.set(key, value, 'EX', EXPIRED, (err, result) => {
            if (!err) {
               resolve(result);
            }
            client.quit();
        });
    });
};

module.exports = set;
//resid.test.js
let mockRedisClient = {
    on: jest.fn(),
    quit: jest.fn()
};
let mockRedis = {
    createClient: jest.fn(() => mockRedisClient),
};
jest.mock('redis', () => mockRedis);
let redis = require('../redis');

describe('redis string', () => {
    test('normal set', () => {
        let set = jest.fn((k, v, ex, timeout, callback) => {
            expect(k).toMatch('key');
            expect(v).toMatch('value');
            expect(ex).toMatch('EX');
            expect(timeout).toBe(24 * 30 * 3600);

            callback(null, 'OK')
        });
        mockRedisClient.set = set;

        return redis.set('key', 'value').then(v => {
            expect(mockRedis.createClient.mock.calls.length).toBe(1);
            expect(set.mock.calls.length).toBe(1);
            expect(mockRedisClient.quit.mock.calls.length).toBe(1);
            expect(v).toMatch('OK');
        });
    });
});

mock module

mock整个js文件, 比较不灵活, 场景比较固定 比如我们在单元测试的时候,不生成日志, 就可以把日志模块mock掉,只要保持api不变即可。

//log.js
const fs = require('fs');
const {createLogger, format, transports} = require('winston');
const {combine, timestamp, printf, splat} = format;
// winston3.0版本不支持按日期滚动
// const DailyRotateFile = require('winston-daily-rotate-file');

 //使用morgan记录express的访问日志
const morgan = require('morgan');

const path = require('path');
const logDir = path.join(__dirname, '../logs');

const myFormat = printf(info => {
    return `${info.timestamp} [${info.level}] ${info.message}`;
});

const ts = {
    console: new transports.Console(),
    debug: new transports.File({filename: logDir + '/debug.log', level: 'debug'}),
    info: new transports.File({filename: logDir + '/info.log', level: 'info'}),
    warn: new transports.File({filename: logDir + '/warn.log', level: 'warn'}),
    error: new transports.File({filename: logDir + '/error.log', level: 'error'}),
};

const debugLogger = createLogger({
    exitOnError: false,
    format: combine(
        timestamp(),
        myFormat
    ),
    transports: [
        ts.debug, ts.console
    ],
});

const infoLogger = createLogger({
    exitOnError: false,
    format: combine(
        timestamp(),
        myFormat
    ),
    transports: [
        ts.info, ts.console
    ],
});

const warnLogger = createLogger({
    exitOnError: false,
    format: combine(
        timestamp(),
        myFormat
    ),
    transports: [
        ts.warn, ts.console
    ],
});

const errorLogger = createLogger({
    exitOnError: false,
    format: combine(
        timestamp(),
        splat(),
        myFormat
    ),
    transports: [
        ts.error, ts.console
    ],
});

/**
 * https://github.com/expressjs/morgan
 */
const custom_format = '[:date[iso]] :remote-addr ":method :url HTTP/:http-version" :status ' +
    ':res[content-length] :response-time ":referrer" ":user-agent"';
const accessLogStream = fs.createWriteStream(path.join(logDir, 'access.log'), {flags: 'a'});
const accessLogger = morgan(custom_format, {stream: accessLogStream});

const Logger = function () {

};

const DefaultLogger = new Logger();

Logger.prototype.debug = function (message) {
    debugLogger.debug(message);
};
Logger.prototype.info = function (message) {
    infoLogger.info(message);
};
Logger.prototype.warn = function (message) {
    warnLogger.warn(message);
};
Logger.prototype.error = function (message, err) {
    if (err)
        errorLogger.error("%s ### %o", message, err);
    errorLogger.error("%s", message);
};
Logger.prototype.fatal = function (message, err) {
    if (err)
        errorLogger.error("%s ### %o", message, err);
    errorLogger.error("%s", message);
};

function getLogger(category) {
    if (!category)
        return DefaultLogger;
    switch (category) {
        case 'access':
            return accessLogger;
            break;
        default:
            return DefaultLogger;
            break;
    }
}

module.exports = {
    getLogger,
};
//mock log.js
const Logger = function () {

};

const DefaultLogger = new Logger();

Logger.prototype.debug = function (message) {
};
Logger.prototype.info = function (message) {
};
Logger.prototype.warn = function (message) {
};
Logger.prototype.error = function (message, err) {
};
Logger.prototype.fatal = function (message, err) {
};

function getLogger(category) {
    return DefaultLogger;
}

module.exports = {
    getLogger
};