Test environment
Node 中的约定是用 NODE_ENV 环境变量定义应用的执行模式
修改package.json
"scripts": {
-- snip --
"start": "NODE_ENV=production node index.js",
"dev": "NODE_ENV=development nodemon index.js",
"test": "NODE_ENV=test jest --verbose --runInBand"
},
—runInBand防止Jest进行运行测试
在脚本中指定应用模式不能在windows上正常工作, 安装cross-env解决这个问题
npm install --save-dev cross-env
再次修改scripts
"scripts": {
-- snip --
"start": "cross-env NODE_ENV=production node index.js",
"dev": "cross-env NODE_ENV=development nodemon index.js",
"test": "cross-env NODE_ENV=test jest --verbose --runInBand",
},
修改config.js
require('dotenv').config()
const PORT = process.env.PORT
const MONGODB_URI = process.env.NODE_ENV === 'test'
? process.env.TEST_MONGODB_URI
: process.env.MONGODB_URI
module.exports = {
MONGODB_URI,
PORT
}
修改.env文件, 添加note-app-test数据库
MONGODB_URI=mongodb+srv://fullstack:secred@cluster0-ostce.mongodb.net/note-app?retryWrites=true
PORT=3001
TEST_MONGODB_URI=mongodb+srv://fullstack:secret@cluster0-ostce.mongodb.net/note-app-test?retryWrites=true
supertest
使用supertest包帮助我们编写API的测试
安装supertest开发依赖
npm install --save-dev supertest
新建test/note_api.test.js
const mongoose = require('mongoose')
const supertest = require('supertest')
const app = require('../app')
const api = supertest(app)
test('notes are returned as json', async () => {
await api
.get('/api/notes')
.expect(200)
.expect('Content-Type', /application\/json/)
})
afterAll(() => {
mongoose.connection.close()
})
如果测试报错,在项目根目录新建jest.config.js文件
module.exports = {
testEnvironment: 'node'
}
或者给test添加第三个参数
test('notes are returned as json', async () => {
await api
.get('/api/notes')
.expect(200)
.expect('Content-Type', /application\/json/)
}, 100000)
再写一些测试
test('there are two notes', async () => {
const response = await api.get('/api/notes')
expect(response.body).toHaveLength(2)
})
test('the first note is about HTTP methods', async () => {
const response = await api.get('/api/notes')
expect(response.body[0].content).toBe('HTML is easy')
})
Initializing the database before tests
在每个test前使用beforeEach初始化数据库
const mongoose = require('mongoose')
const supertest = require('supertest')
const app = require('../app')
const api = supertest(app)
const Note = require('../models/note')
const initialNotes = [
{
content: 'HTML is easy',
date: new Date(),
important: false,
},
{
content: 'Browser can execute only Javascript',
date: new Date(),
important: true
}
]
beforeEach(async () => {
await Note.deleteMany({})
let noteObject = new Note(initialNotes[0])
await noteObject.save()
noteObject = new Note(initialNotes[1])
await noteObject.save()
})
test('notes are returned as json', async () => {
await api
.get('/api/notes')
.expect(200)
.expect('Content-Type', /application\/json/)
}, 100000)
-- snip --
修改最后2个测试
test('all notes are returned', async () => {
const response = await api.get('/api/notes')
expect(response.body).toHaveLength(initialNotes.length)
})
test('a specific note is within the returned notes', async () => {
const response = await api.get('/api/notes')
const contents = response.body.map(r => r.content)
expect(contents).toContain('Browser can execute only Javascript')
})
Running tests one by one
运行指定文件的测试
npm test -- tests/note_api.test.js
-t 选项可用于运行具有特定名称的测试
npm test -- -t "a specific note is within the returned notes"
运行包含关键字的测试
npm test -- -t 'notes'
async/await
async/await目的是使用异步调用函数来返回一个 promise,但使代码看起来像是同步调用。
普通的promise
Note.find({})
.then(notes => {
return notes[0].remove()
})
.then(response => {
console.log('the first note is removed')
// more code here
})
改用async/await
const notes = await Note.find({})
const response = await notes[0].remove()
console.log('the first note is removed')
async/await避免了写回调函数
代码执行会在await处暂停等待,直到promise settle了才继续往下执行
需要注意的是: await返回的必须是一个promise, 且只能在async声明的函数中使用
async/await in the backend
notesRouter.get('/', (request, response) => {
Note.find({}).then(notes => {
response.json(notes)
})
})
改为使用async/await
notesRouter.get('/', async (request, response) => {
const notes = await Note.find({})
response.json(notes)
})
Error handling and async/await
使用try…catch处理async/await错误
notesRouter.post('/', async (request, response, next) => {
const body = request.body
const note = new Note({
content: body.content,
important: body.important || false,
date: new Date()
})
try {
const savedNote = await note.save()
response.json(savedNote)
} catch(exception) {
next(exception)
}
})
Eliminating the try-catch
使用express-async-errors库可以消除try…catch
安装npm install express-async-errors
在app.js中引入库
const config = require('./utils/config')
const express = require('express')
require('express-async-errors')
-- snip --
module.exports = app
现在可以直接将try…catch去掉,next(exception)也不再需要,库会自动处理错误
notesRouter.delete('/:id', async (request, response) => {
await Note.findByIdAndRemove(request.params.id)
response.status(204).end()
})
Optimizing the beforeEach function
使用Promise.all
beforeEach(async () => {
await Note.deleteMany({})
const noteObjects = helper.initialNotes
.map(note => new Note(note))
const promiseArray = noteObjects.map(note => note.save())
await Promise.all(promiseArray)
})
await Promise.all会等待所有的promise都完成