- Published on
MongoDB testing
อย่างที่เคยบอกในบทความก่อนหน้าว่ามีโอกาสได้เขียนเทส เลยอยากบันทึกความรู้นี้เก็บไว้ วันนี้เจมส์จะมาเขียนสิ่งที่ได้เรียนรู้เกี่ยวกับการเขียน Test MongoDB ครับ
สิ่งที่จะทำวันนี้ เราทำแบบง่าย ๆ เหมือนเดิมเน้อครับ ก็คือเราจะสร้าง API ขึ้นมา 2 route คือ
GET
/api/todos => ดึงค่าจาก MongoDB มาแสดงPOST
/api/todo => เพิ่มข้อมูลสิ่งที่จะทำใส่ใน MongoDB
มาเริ่มกันเลยดีกว่าครับ
ขั้นแรกเดี๋ยวเรามาสร้าง mongodb จาก docker กันดีกว่าครับ แต่เพื่อความสะดวก เจมส์จะใช้ docker-compose.yaml ในการช่วยสร้าง (เจมส์เคยเขียนบทความไว้แล้วครับ แต่เป็น Blog อันเก่าครับ [MongoDB] docker-compose up ปุ๊บสร้าง mongo database ปั๊บ)
สร้าง mongo จาก Docker โดยใช้ docker-compose
ขั้นแรกเรามาสร้าง Project ขึ้นมาก่อนครับ โดยการพิมพ์คำสั่ง
mkdir mongodb-testing
cd mongodb-testing
จากนั้นสร้างไฟล์ docker-compose.yaml
ก่อนครับ และใส่ข้อมูลดังนี้ครับ
version: "3.7"
services:
database:
image: mongo:6.0.2
ports:
- 27017:27017
environment:
- MONGO_INITDB_ROOT_USERNAME=root
- MONGO_INITDB_ROOT_PASSWORD=123456
- MONGO_INITDB_DATABASE=example
- MONGO_INITDB_USERNAME=kajame
- MONGO_INITDB_PASSWORD=111111
volumes:
- ./init-mongo.sh:/docker-entrypoint-initdb.d/init-mongo.sh
- ./db:/data/db
และสร้างไฟล์ init-mongo.sh
และใส่โค้ดดังนี้ครับ
mongosh -- "$MONGO_INITDB_DATABASE" <<EOF
var rootUser = '$MONGO_INITDB_ROOT_USERNAME';
var rootPassword = '$MONGO_INITDB_ROOT_PASSWORD';
var admin = db.getSiblingDB('admin');
admin.auth(rootUser, rootPassword);
var user = '$MONGO_INITDB_USERNAME';
var passwd = '$MONGO_INITDB_PASSWORD';
db.createUser({
user: user,
pwd: passwd,
roles: ["readWrite"]
});
EOF
จากนั้นใช้คำสั่ง docker-compose up
ใน terminal ครับ (ที่ root project ของเรา)
docker-compose up
สร้าง package.json และ install dependencies
สร้าง package.json ด้วยคำสั่ง
npm init -y
เราจะ install express
, body-parser
, mongoose
ด้วยคำสั่ง
npm install --save express body-parser mongoose
จากนั้น install jest
, mongodb-memory-server
ใน dev dependencies ครับ
npm install --save-dev jest mongodb-memory-server
จากนั้นเราจะสร้างไฟล์ชื่อ server.js
ขึ้นมาครับ
const express = require('express')
const mongoose = require('mongoose')
const bodyParser = require('body-parser')
const { getTodos, createTodo } = require('./services/todo')
const app = express()
const PORT = 2000
// parse application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// parse application/json
app.use(bodyParser.json())
app.get('/api/todos', async (req, res) => {
const todos = await getTodos()
return res.json(todos)
})
app.post('/api/todo', async (req, res) => {
const { task } = req.body
if(!task || task.trim() === '') {
return res.status(400).json({ success: false, message: 'Bad request' })
}
const todo = await createTodo({ task })
return res.json(todo)
})
app.listen(PORT, () => {
console.log(`Running on port: ${PORT}`)
})
mongoose.set("strictQuery", true);
mongoose.connect(`mongodb://kajame:111111@localhost:27017/example`)
const db = mongoose.connection
db.on('error', () => {
console.log(`mongoose connection failed`)
})
db.once('open', () => {
console.log(`mongoose connected`)
})
จากนั้นสร้าง directory ชื่อ services
และสร้างไฟล์ชื่อ todo.js
ขึ้นมาครับ
const Todo = require("../models/Todo")
async function getTodos() {
const todos = await Todo.find({})
return todos
}
async function createTodo({ task, status = 'pending' }) {
try {
const todo = await Todo.create({ task, status })
return { success: true, _id: todo._id }
} catch(error) {
return { success: false }
}
}
module.exports = {
getTodos,
createTodo
}
จากนั้นสร้าง directory ชื่อ models
และสร้างไฟล์ชื่อ Todo.js
ขึ้นมาครับ
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const TodoSchema = new Schema({
task: String,
status: {
type: String,
enum: ['pending', 'completed']
}
}, { timestamps: true })
const Todo = mongoose.model('Todo', TodoSchema)
module.exports = Todo
จากนั้นให้เราสร้าง directory ชื่อ __mock__
ใน directory services
และสร้างไฟล์ชื่อ database.js
ขึ้นมาครับ
const mongoose = require('mongoose')
const mockTodos = [
{
"_id" : mongoose.Types.ObjectId("63baf3d18f8a71f2531cca60"),
"task" : "test",
"status" : "pending",
"createdAt" : new Date("2023-01-08T23:48:17.103+07:00"),
"updatedAt" : new Date("2023-01-08T23:48:17.103+07:00"),
"__v" : 0
}
]
module.exports = {
mockTodos
}
สุดท้ายสร้างไฟล์ชื่อ todo.test.js
ใน directory services
ดังนี้ครับ
const mongoose = require('mongoose')
const { MongoMemoryServer } = require('mongodb-memory-server')
const { getTodos, createTodo } = require('./todo')
const Todo = require('../models/Todo')
const { mockTodos } = require('./__mock__/database')
async function initDefaultData() {
Todo.create()
await Promise.all([
Todo.create(mockTodos),
])
}
let mongod = null
beforeAll(async () => {
mongod = await MongoMemoryServer.create()
const dbUrl = mongod.getUri()
await mongoose.connect(dbUrl)
await initDefaultData()
})
afterAll(async () => {
await mongoose.connection.close()
await mongod.stop()
})
describe('getTodos function', () => {
it('Should return todos length equal 1', async () => {
const todos = await getTodos()
expect(todos).toMatchObject(mockTodos)
expect(todos.length).toEqual(1)
})
})
describe('createTodo function', () => {
it('Should return success', async () => {
const task = 'Test MongoDB'
const todo = await createTodo({ task })
expect(todo.success).toEqual(true)
expect(todo._id).not.toBe(undefined)
})
it('Should have todos length equal 2', async () => {
const todos = await getTodos()
expect(todos.length).toEqual(2)
})
})
เราจะมาคุยกันแต่ละส่วนดีกว่าครับว่าไฟล์ที่สร้างทำอะไรบ้าง
- models/Todo.js => คือ mongoose model ของ Todo collection ครับ
- services/mock/database.js => เราจะ mock ข้อมูล todos ขึ้นมาครับ ซึ่งอันนี้เราจะใช้สำหรับ Test อย่างเดียวครับ
- services/todo.test.js => เป็นไฟล์สำหรับ test (เราจะดูไฟล์นี้เป็นหลักนะครับสำหรับบทความนี้)
- services/todo.js => เป็นไฟล์ที่เจมส์เขียนแยกการทำงานของ todo ออกมาเป็นฟังก์ชันไว้ครับ ซึี่งมี 2 ส่วนคือ getTodo กับ createTodo (เราจะ Test ไฟล์นี้แหละครับ)
- server.js => เป็น API สำหรับลอง Run แบบจริง ๆ
services/todo.test.js
เรามาดูไฟล์ todo.test.js
กันดีกว่าครับว่าเจมส์ Test อะไรยังไงบ้าง
สิ่งแรกคือเจมส์ import mongodb-memory-server
มาใช้งาน คือเอาไว้จำลอง mongodb จะได้ไม่กระทบกับของจริงครับ
ในฟังก์ชัน initDefaultData
เจมส์ก็ให้สร้างข้อมูลที่ Mock ขึ้นมา โดย import มาจาก services/__mock__/database.js
นั่นเอง
ในส่วนฟังก์ชัน beforeAll
คือก่อน Test จะให้ตั้งค่าต่าง ๆ ในฟังก์ชั่นนี้ ซึ่งในที่นี้คือจะสร้าง mongoDB จำลองขึ้นมา และเรียกฟังก์ชันสำหรับเอาค่าที่เรา mock ไว้มาใส่
afterAll
จะเคลียร์ค่าต่าง ๆ เมื่อ Run คำสั่ง Test ต่าง ๆ เรียบร้อยแล้ว ซึ่งในที่นี้คือ close connection ของ mongoose และ stop mongoDB ที่เราจำลองขึ้นมา
ในส่วนการ Test สำหรับฟังก์ชั่น getTodos
เจมส์ทดสอบโดยการเรียกใช้ฟังก์ชันที่สร้าง ซึ่งมันดึงค่า todos จาก mongo มา
ในที่นี้จะมีค่าที่เรา mock ไว้ในตอนต้น ซึ่งสิ่งที่เราคาดหวังจากการ Test นี้คือ ข้อมูลที่ได้จาก getTodos จะมีค่าเท่ากับค่าที่ mock ไว้
ในส่วนการ Test สำหรับฟังก์ชัน createTodo
เจมส์ทดสอบโดยการเรียกใช้ฟังก์ชันที่สร้าง โดย Test ว่าเมื่อส่ง task เข้าไปแล้ว มันจะ return success เป็น true ออกมา และมีค่า _id response กลับมาด้วย
จากนั้นก็ลอง Test โดยการเรียกใช้ getTodos
และคาดหวังว่าจะมีข้อมูลทั้งหมดคือ 2 ข้อมูล
ปรับไฟล์ package.json
ปรับไฟล์ package.json
อีกนิดครับ ในส่วนของ script เราจะปรับเป็นแบบนี้ครับ
"scripts": {
"start": "node server.js",
"test": "jest"
},
เมื่อเราลอง run คำสั่ง
npm run test
จะพบว่า ผ่านหมด ซึ่งการทำงานก็ตามที่ได้อธิบายไปก่อนหน้าแล้ว
เมื่อเราลอง run คำสั่ง
npm run start
แล้วลองเข้า url: http://localhost:2000/api/todos จะพบว่าไม่มีข้อมูล
สิ่งที่เจมส์จะสื่อคือว่า run test ในส่วนที่เกี่ยวข้องกับ mongoDB มันจะไม่ยุ่งกับ mongoDB ของจริง ทำให้ทดสอบได้อย่างปลอดภัย หายห่วง ^^
หากบทความนี้มีส่วนไหนผิดพลาด ก็ขออภัยมา ณ ที่นี้ด้วยเน้อครับ ยังไงถ้าติดตรงไหนก็สามารถทักแชทมาแจ้งก็ได้เน้อครับ เจมส์จะได้อัพเดทให้ครับ