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