Is it possible to create an Express.js server in my Jest test suite?

雨燕双飞 提交于 2020-07-07 05:36:28

问题


I'm trying to test a function that gets data from an external API using axios. To keep my test function as close as possible to the real thing, I'm querying mock data I have in a file. Axios can't return data from local files, which is a security feature. So the solution I'm trying is spinning up a simple server in my test suite, serving the file there, and then running my tests.

My test suite looks like this right now:

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');
    });
}

The functions I'm testing look like this (the one that uses Axios is getFeedData, so I don't think the others are causing a problem but I'm not positive).

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;
        }
    }
}

Here's the error message Jest is giving me:

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)```

My best guess at the issue is that maybe Jest doesn't run in a node environment (is there any way I can figure that out)? So maybe the Express server isn't being run at all. However, I'm a little beyond my expertise so that is little more than a guess. Is anyone able to shed a little light on what's really going on? Was my idea to run the Express server a good one? Was it a good idea to put it in the test suite? If the answer to one or both of those questions is "no," what are best practices here?


回答1:


You should create your server beforeEach() (and stop it in afterEach()) so that it runs for each test.

docs.

You should also pick an unused port so that tests can run in parallel.




回答2:


To avoid code duplication between all your source files, you can create a node environment, which will be setup for all your tests files:

package.json

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

testEnvironment.js

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

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;

Then, you can access the this.global.server in your tests files to get the server port/address:

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}'...`)
});

Results:

$ 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.

Notice the documentation warning:

Note: TestEnvironment is sandboxed. Each test suite/file will trigger setup/teardown in their own TestEnvironment.

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




回答3:


Alternatively, you can use globalSetup and globalTeardown combined with testEnvironment

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;


来源:https://stackoverflow.com/questions/47643150/is-it-possible-to-create-an-express-js-server-in-my-jest-test-suite

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!