问题
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