q.js and Mocha
Published on Nov 10, 2013
ปัญหาที่น่าปวดหัวอย่างหนึ่งเวลาเขียน spec คู่กับ q.js คือ q.js จัดการ exception ให้เวลา expect แล้ว fail, exception ที่ throw จาก spec เลยไม่ถูกส่งออกมาถึง spec กลายเป็นว่า spec นั้นผ่านเพราะไม่มี error อะไร หรือถ้าเป็น async เวลาเรียก done ใน q ก็เหมือนมีตัวแปรชื่อเดียวกันอีก กลายเป็นว่า done ของ spec ไม่ถูกเรียกไปเรียกของ q แทน ตัวอย่าง spec ง่ายๆ เช่น
function authenticate(req, res) {
q.nfcall(user.authenticate, req.body.username, req.body.password)
.then(function (result) {
res.json(result)
})
.fail(function (err) {
res.json(err)
})
}
เวลาเขียน spec อย่างง่ายๆ ที่คาดหวังคือ
describe('#authenticate', function () {
it('should return error when password is wrong', function (done) {
authenticate(
{ username: 'username', password: 'wrongpassword' },
{
json: function json(status, data) {
expect(output.status).to.equal(200)
expect(output.data.message).to.equal('Wrong password')
done()
}
}
)
})
})
ซึ่งควรจะรันแบบผ่านไปได้ด้วยดี ถ้ามี error ก็แสดงออกมาแต่หลังจากรัน mocha จะ error เพราะ timeout (done ไม่ถูกเรียก) ทางแก้ง่ายสุดคือใช้ flag เข้าช่วยแล้วให้ interval คอยตรวจว่า flag เปลี่ยนไปหรือยังได้ออกมาเป็น specs version 2 (ใน test_it หรือ jasmine ใช้ waitFor ได้)
describe('#authenticate', function () {
it('should return error when password is wrong', function (done) {
var flag = false
var output = {}
authenticate(
{ username: 'username', password: 'wrongpassword' },
{
json: function (status, data) {
flag = true
output.status = status
output.data = data
}
}
)
var interval = setInterval(function () {
if (!flag) return
clearInterval(interval)
expect(output.status).to.equal(200)
expect(output.data.message).to.equal('Wrong password')
done()
}, 100)
})
})
ซึ่งหน้าตายาวเหยียดและดูไม่น่าเขียนเท่าไหร่ เลยหาทางกำจัด flag กับ setInterval ก่อนออกมาเป็น version 3
describe('#authenticate', function () {
it('should return error when password is wrong', function (done) {
var deferred = q.defer()
var output = null
deferred.promise.then(function () {
expect(output.status).to.equal(200)
expect(output.data.message).to.equal('Wrong password')
done()
})
authenticate(
{ username: 'username', password: 'wrongpassword' },
{
json: function (status, data) {
output.status = status
output.data = data
deferred.resolve()
}
}
)
})
})
สิ่งที่ยังกวนใจอยู่คือ output ที่มันควรส่งเข้าไปให้ test ตรงๆ ได้ไม่ต้องมาสร้าง variable รับอีกเลยใช้ spread ช่วย
describe('#authenticate', function () {
it('should return error when password is wrong', function (done) {
var deferred = q.defer()
deferred.promise.spread(function (status, data) {
expect(status).to.equal(200)
expect(data.message).to.equal('Wrong password')
done()
})
authenticate(
{ username: 'username', password: 'wrongpassword' },
{
json: function (status, data) {
deferred.resolve([status, data])
}
}
)
})
})
นี่แหละที่ต้องการมี code เพิ่มนิดหน่อยเกือบจะเท่าแบบแรกสุดแถมดูดีอีกต่างหาก ไม่มีตัวแปรอื่นมากวนเท่าไหร่ เขียนมานี่ตอนลองมั่วอยู่นานกว่าจะได้ แต่ก่อนใช้ mocha-as-promise ช่วยแต่พบว่ามันดันให้ผ่านใน test ที่ควรจะ fail เลยเลิกใช้เลย