jest
&supertest
を使用してAPIエンドポイントをテストしていますが、テストは問題なく合格しています。ただし、jest
は、通常表示されるDone in X timeステートメントで終了することはありません。 _--watch
_フラグが使用されていないことを確認しましたが、使用されていませんでした。サーバーへの接続が決して終了しないように見えるので、jest
は次に何が起こるのか疑問に思っています。 UserFactory.generate()
は、faker
ライブラリを使用して偽のユーザーデータを作成します。
私はこれをトラブルシューティングしようとして途方に暮れています。 jest
のヘルプページで推奨されている戦略をうまく実行できず、課題トラッカーも調べていましたが、同様の問題については何もわかりません。
これは、テストスイートを実行したときに表示されるものです。
ご覧のとおり、テストは7回実行されています。すべてのテストが実行されたと言われ、morgan
は発生したPOST
を示します。 jest
neverexitsしたがって、手動で終了しない限り、CIサーバーでタイムアウトになるため、実際には失敗します。
_async/await
_の代わりに.end() & done()
と.then() & done()
を使用してみました。それらはすべて同じ最終結果を返し、それが未解決の約束の問題である場合、jest
は未解決の約束でエラーになるので、なぜこれがjest
として終了しないのかについて私は困惑しています。通常はそうします。
誰かが以前にこのような問題に遭遇したことがありますか?
user.controller.test.js
_import mongoose from 'mongoose';
import request from 'supertest';
import { UserFactory } from '../../__mocks__';
import { User } from '../../modules';
import { config } from '../../utils';
import app from '../../';
const mockRoute = data => request(app).post(`${config.ENDPOINT}/user/sign-up`).send(data);
describe(`POST: /user/sign-up`, () => {
// remove any user data from db prior to running tests.
beforeAll(async () => { await User.remove(); });
test('Returns status 201 on success.', async () => {
// Returns the response object: res.status === { status }
const { status } = await mockRoute(UserFactory.generate());
expect(status).toEqual(201);
});
afterAll(async () => {
// drop connection to the collection
const { users } = mongoose.connection.collections;
await users.drop();
});
});
_
user/routers.js
_import { Router } from 'express';
import validate from 'express-validation';
import { signUp } from './controller';
import valid from './validation'
const routes = new Router();
/**
* 1. Define the route: 'user/signup'.
* 2. Validate the data being provided on the POST
* against valid.signUp object.
* 3. Provide data to signUp controller method for
* creating a user in the database.
*/
routes.post('/user/sign-up', validate(valid.signUp), signUp);
export default routes;
_
user/controller.js
_import HTTPStatus from 'http-status';
import User from './model';
import { config, filterBody } from '../../utils';
export const signUp = async (req, res, next) => {
const filteredBody = filterBody(req.body, config.WHITELIST.users.signUp);
try {
const user = await User.create(filteredBody);
return res.status(HTTPStatus.CREATED).json(user.toAuthJSON());
} catch (e) {
e.status = HTTPStatus.BAD_REQUEST;
return next(e);
}
}
_
user/model.js
_import mongoose, { Schema } from 'mongoose';
import uniqueValidator from 'mongoose-unique-validator';
import { hashSync, compareSync } from 'bcrypt-nodejs';
import jwt from 'jsonwebtoken';
import { config } from '../../utils';
const UserSchema = new Schema({
email: {
type: String,
unique: true,
required: [true, 'Email is required!'],
trim: true,
validate: {
validator(email) {
const emailRegex = /^[-a-z0-9%S_+]+(\.[-a-z0-9%S_+]+)*@(?:[a-z0-9-]{1,63}\.){1,125}[a-z]{2,63}$/i;
return emailRegex.test(email);
},
message: '{VALUE} is not a valid email!',
}
},
password: {
type: String,
required: [true, 'Password is required!'],
trim: true,
minlength: [6, 'Password need to be longer!'],
validate: {
validator(password) {
return password.length >= 6 && password.match(/\d+/g);
},
},
}
}, { timestamps: true })
UserSchema.plugin(uniqueValidator, {
message: '{VALUE} already taken!',
});
UserSchema.pre('save', function(next) {
if (this.isModified('password')) {
this.password = this._hashPassword(this.password);
return next();
}
return next();
});
UserSchema.methods = {
authenticateUser(password) {
return compareSync(password, this.password);
},
_hashPassword(password) {
return hashSync(password);
},
createToken() {
return jwt.sign({ _id: this._id }, config.JWT_SECRET);
},
toAuthJSON() {
return {
_id: this._id,
token: `JWT ${this.createToken()}`,
};
},
toJSON() {
return {
_id: this._id,
username: this.username,
};
},
};
let User;
try {
User = mongoose.model('User');
} catch (e) {
User = mongoose.model('User', UserSchema);
}
export default User;
_
user/validation.js
_import Joi from 'joi';
export default {
signUp: {
body: {
email: Joi.string().email().required(),
password: Joi.string()
.min(6)
.regex(/^(?=.*[0-9])(?=.*[a-zA-Z])([a-zA-Z0-9]+)$/)
.required(),
},
},
};
_
middlewares.js
_import bodyParser from 'body-parser';
import compression from 'compression';
import cors from 'cors';
import morgan from 'morgan';
import { userRoutes } from '../modules';
export default app => {
app.use(compression());
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cors());
app.use(morgan('dev'));
// applying api routes last.
app.use('/api/v1', [userRoutes]);
}
_
index.js
_import express from 'express';
import {
database,
config,
middlewares,
} from './utils';
// Create instance of Express.
const app = express();
const MODE = process.env.NODE_ENV;
// Apply middlewares to Express.
middlewares(app);
// Connect to the environment determined database.
database(config.MONGO_URI)
app.listen(config.PORT, err => {
if (err) { return console.error(err); }
console.log(`App running on port: ${config.PORT} in ${MODE} mode.`);
});
export default app;
_
問題は、afterAll()
フックで十分な処理を行っていなかったことでした。
問題を再現してそこからトラブルシューティングするために小さなリポジトリを作成しました。これは、テストスイートが正常に完了したときにjest
が終了できるようにするために必要なことでした。
afterAll(async () => {
try {
const { todos } = mongoose.connection.collections;
// Collection is being dropped.
await todos.drop()
// Connection to Mongo killed.
await mongoose.disconnect();
// Server connection closed.
await server.close();
} catch (error) {
console.log(`
You did something wrong dummy!
${error}
`);
throw error;
}