【问题标题】:Is it possible to create an Express.js server in my Jest test suite?是否可以在我的 Jest 测试套件中创建 Express.js 服务器?
【发布时间】:2017-12-04 22:38:58
【问题描述】:

我正在尝试测试一个使用 axios 从外部 API 获取数据的函数。为了使我的测试功能尽可能接近真实情况,我正在查询文件中的模拟数据。 axios 不能从本地文件返回数据,这是一个安全特性。所以我正在尝试的解决方案是在我的测试套件中启动一个简单的服务器,在那里提供文件,然后运行我的测试。

我的测试套件现在看起来像这样:

import React from 'react';
import {shallow} from 'enzyme';
import express from 'express';
import { getFeedId, getFeedData, reverseStop } from '../mocks/apiMock';

const app = express();
const port = 4000;
app.use(express.static('../mocks/MockData.json'));
app.listen(port, tests());

function tests () {
    it('returns the expected feed id for a given subway line', () => {
        expect(getFeedId('L')).toBe(2);
    });

    it('returns json data', () => {
        expect.assertions(2);
        return getFeedData('L').then(data => {
            expect(data).toBeDefined();
            expect(data.header.gtfs_realtime_version).toBe('1.0');
        });
    });

    it('returns a stop_id for a given subway line and stop', () => {
        expect(reverseStop('L', 'Lorimer St')).toBe('L10N');
    });
}

我正在测试的函数看起来是这样的(使用Axios的是getFeedData,所以我认为其他的没有问题但我不肯定)。

const axios = require('axios');

export function getFeedId (sub) {
    switch (sub) {
        case '1': case '2': case '3': case '4': case '5': case '6': case 'S':
            return 1;
        case 'A': case 'C': case 'E':
            return 26;
        case 'N': case 'Q': case 'R': case 'W':
            return 16;
        case 'B': case 'D': case 'F': case 'M':
            return 21;
        case 'L':
            return 2;
        case 'G':
            return 31;
    }
}

export function getFeedData (sub) {
    if (getFeedId(sub) === 2) {
        return axios.get('http://localhost:4000').then((data) => JSON.parse(data));
    }
}

export function reverseStop (sub, stop) {
    const stops = require('../utils/stops');
    const stopObjs = stops.filter((item) => item.stop_name == stop && typeof item.stop_id === 'string' && item.stop_id.charAt(0) == sub);
    for (var i = 0; i < stopObjs.length; i++) {
        if (stopObjs[i].stop_id.charAt(stopObjs[i].stop_id.length - 1) == 'N') {
            return stopObjs[i].stop_id;
        }
    }
}

这是 Jest 给我的错误消息:

FAIL  src/tests/api.test.js (23.311s)
  ● returns json data

Network Error

  at createError (node_modules/axios/lib/core/createError.js:16:15)
  at XMLHttpRequest.handleError [as onerror] (node_modules/axios/lib/adapters/xhr.js:87:14)
  at XMLHttpRequest.callback.(anonymous function) (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:289:32)
  at invokeEventListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:219:27)
  at invokeInlineListeners (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:166:7)
  at EventTargetImpl._dispatch (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:122:7)
  at EventTargetImpl.dispatchEvent (node_modules/jsdom/lib/jsdom/living/events/EventTarget-impl.js:87:17)
  at XMLHttpRequest.dispatchEvent (node_modules/jsdom/lib/jsdom/living/generated/EventTarget.js:61:35)
  at dispatchError (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:994:9)
  at validCORSHeaders (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:1009:7)
  at receiveResponse (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:871:12)
  at Request.client.on.res (node_modules/jsdom/lib/jsdom/living/xmlhttprequest.js:691:38)
  at emitOne (events.js:96:13)
  at Request.emit (events.js:191:7)
  at Request.onRequestResponse (node_modules/request/request.js:1074:10)
  at emitOne (events.js:96:13)
  at ClientRequest.emit (events.js:191:7)
  at HTTPParser.parserOnIncomingClient (_http_client.js:522:21)
  at HTTPParser.parserOnHeadersComplete (_http_common.js:99:23)
  at Socket.socketOnData (_http_client.js:411:20)
  at emitOne (events.js:96:13)
  at Socket.emit (events.js:191:7)
  at readableAddChunk (_stream_readable.js:178:18)
  at Socket.Readable.push (_stream_readable.js:136:10)
  at TCP.onread (net.js:560:20)

  ● returns json data

expect.assertions(2)

Expected two assertions to be called but only received zero assertion calls.

  at addAssertionErrors (node_modules/jest-jasmine2/build/setup-jest-globals.js:68:21)
  at process._tickCallback (internal/process/next_tick.js:109:7)```

我对这个问题的最佳猜测是,也许 Jest 不能在节点环境中运行(有什么办法可以弄清楚)?所以也许 Express 服务器根本没有运行。然而,我有点超出我的专业知识,所以这只是一个猜测。有没有人能够对真正发生的事情有所了解?我运行 Express 服务器的想法是一个好主意吗?把它放在测试套件中是个好主意吗?如果其中一个或两个问题的答案都是“否”,那么这里的最佳做法是什么?

【问题讨论】:

  • 如果它不在 Node 中运行,Express 本身会报错。
  • 旁注:您实际上并没有将回调传递给listen()。通过一个可能会解决您的问题。
  • 我没有通过回调来监听?我认为将tests() 作为第二个参数传递是我的回调。此外,您在下面说使beforeEach() 异步并将回调传递给listen()listen() 不应该包含在 beforeEach() 中吗?
  • 否;您正在调用 tests() 并传递它的返回值(即undefined)。就像任何其他函数调用一样。
  • 您应该在beforeEach() 中调用listen() 并将来自beforeEach()done 回调传递给它。阅读文档并了解回调的工作原理。

标签: javascript node.js unit-testing express jestjs


【解决方案1】:

您应该创建您的服务器 beforeEach()(并在 afterEach() 中停止它),以便它为每个测试运行。

docs.

您还应该选择一个未使用的端口,以便可以并行运行测试。

【讨论】:

  • 如何选择未使用的端口?我需要让 Express 监听随机端口吗?如果我没记错的话,这样做的方法是将app.listen() 留空,那么我将如何添加我的回调呢?另外,如果我正在创建服务器 beforeEach(),我是否需要更改我的回调?
  • @bkula:通过0nodejs.org/api/…
  • 谢谢,但我仍然对回调的外观感到困惑。如果我通过app.listen() 一个运行每个测试的回调,这不会破坏使用beforeEach() 的目的吗?我需要以某种方式遍历它们吗?或者beforeEach() 中的代码会在每次测试之前运行吗?
  • 你应该使beforeEach()异步并将其回调传递给listen()
  • 感谢所有帮助,您为我指明了正确的方向,但我仍然对这段代码的外观一无所知。
【解决方案2】:

为避免所有源文件之间的代码重复,您可以创建一个节点环境,该环境将为您的所有测试文件设置:

package.json

{
  "name": "my-project",
  "jest": {
    "testEnvironment": "./testEnvironment.js"
  }
}

testEnvironment.js

const express = require('express');
// for server node apps
// const NodeEnvironment = require('jest-environment-node');

// for browser js apps
const NodeEnvironment = require('jest-environment-jsdom');

class ExpressEnvironment extends NodeEnvironment {
    constructor(config, context) {
        super(config, context);
    }

    async setup() {
        await super.setup();
        let server;
        const app = express();
        await new Promise(function(resolve) {
            server = app.listen(0, "127.0.0.1", function() {
                let address = server.address();
                console.log(
                    ` Running server on '${JSON.stringify(address)}'...`);
                resolve();
            });
        });
        let address = server.address();
        this.global.server = server;
        this.global.address = `${address.address}:${address.port}`
        app.use(express.static('./testfiles'));
    }
    async teardown() {
        this.global.server.close();
        await super.teardown();
    }

    runScript(script) {
        return super.runScript(script);
    }
}

module.exports = ExpressEnvironment;

然后,您可以访问测试文件中的this.global.server 以获取服务器端口/地址:

test.js

test('Show the server address as example', () => {
    // @ts-ignore: https://github.com/kulshekhar/ts-jest/issues/1533
    let address = global.address;
    console.log(`The server address is '${address}'...`)
});

结果:

$ npx jest
 PASS  src/reviewer.test.ts (5.391s)
  √ renders GitHub Repository Researcher site name (10ms)

Running server on '{"address":"127.0.0.1","family":"IPv4","port":50875}'.
  console.log src/reviewer.test.ts:25
    The server address is '127.0.0.1:50875'.

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        6.811s
Ran all test suites.

记住文档警告:

注意:TestEnvironment 是沙盒的。每个测试suite/file都会在他们自己的TestEnvironment中触发setup/teardown。

https://github.com/heuels/jest/blob/master/docs/Configuration.md#available-in-jest-2200

【讨论】:

    【解决方案3】:

    或者,您可以将globalSetupglobalTeardowntestEnvironment 结合使用

    package.json

    {
      "name": "my-project",
      "jest": {
        "testEnvironment": "./testEnvironment.js",
        "globalSetup": "./globalSetup.js",
        "globalTeardown": "./globalTeardown.js"
      }
    }
    

    globalTeardown.js

    module.exports = async () => {
        global.server.close();
    };
    

    globalSetup.js

    const express = require('express');
    
    module.exports = async () => {
        let server;
        const app = express();
    
        await new Promise(function(resolve) {
            server = app.listen(0, "127.0.0.1", function() {
                let address = server.address();
                console.log(` Running express on '${JSON.stringify(address)}'...`);
                resolve();
            });
        });
    
        let address = server.address()
        global.server = server;
        process.env.SERVER_ADDRESS = `http://${address.address}:${address.port}`
        app.use(express.static('./testfiles'));
    };
    

    testEnvironment.js

    const TestEnvironment = require('jest-environment-jsdom'); // for browser js apps
    // const TestEnvironment = require('jest-environment-node'); // for server node apps
    
    class ExpressEnvironment extends TestEnvironment {
        constructor(config, context) {
            let cloneconfig = Object.assign({}, config)
            cloneconfig.testURL = process.env.SERVER_ADDRESS;
            super(cloneconfig, context);
        }
    
        async setup() {
            this.global.jsdom = this.dom;
            await super.setup();
        }
    
        async teardown() {
            this.global.jsdom = null;
            await super.teardown();
        }
    
        runScript(script) {
            return super.runScript(script);
        }
    }
    
    module.exports = ExpressEnvironment;
    

    【讨论】:

      猜你喜欢
      • 2020-06-25
      • 1970-01-01
      • 1970-01-01
      • 2016-12-06
      • 1970-01-01
      • 2015-10-18
      • 2015-07-01
      • 2017-12-14
      • 1970-01-01
      相关资源
      最近更新 更多