Skip to main content
0
Back-End DeveloperDevInit

การใช้ Jest และ SuperTest เพื่อทดสอบ API โดยใช้ฟังก์ชันจำลอง ใน Node.js

การทดสอบการทำงานฟังก์ชันต่าง ๆ ของโปรแกรมว่าสามารถทำงานได้ตามที่คาดหวังหรือไม่ มีความสำคัญเป็นอย่างมาก เพื่อให้มั่นใจได้ว่าเมื่อนำฟังก์ชันต่าง ๆ มาทำงานร่วมกันแล้ว ทุกอย่างจะสามารถทำงานได้โดยไม่เกิดความผิดพลาดใด ๆ ซึ่ง Jest เป็น Framework หนึ่งที่ช่วยให้สามารถทำการตรวจสอบการทำงานของฟังก์ชัน ๆ ได้อย่างมีประสิทธิภาพ สามารถศึกษาการใช้งาน Jest เบื้องต้นได้จากบทความนี้ ทำ Unit Test เบื้องต้นบน Node.Js ด้วย Jest

พื้นฐานที่ต้องมีก่อนจะทำตามบทความนี้

  • Jest ควรเข้าใจฟังก์ชันพื้นฐานของ Jest และการใช้งาน Jest เบื้องต้น
  • การสร้าง API สำหรับการลงทะเบียนและการเรียกรู้ข้อมูลผู้ใช้งานในระบบ

เริ่มต้น Setup โปรเจคกันเลย

ขั้นแรกก็ทำการสร้างโปรเจคโดยใช้คำสั่ง npm init ใน Terminal จากนั้นทำการติดตั้ง express jest และ supertest โดยใช้คำส่ง npm install express jest supertest ซึ่งเมื่อติดตั้งเสร็จแล้วในไฟล์ package.json ควรจะปรากฎ Library ที่ลงไว้ ดังรูป

จากรูปด้านบนจะเห็นว่า ในโค้ดบรรทัดที่ 7 ส่วนของ “test” ได้กำหนดค่าเป็น “jest” เพื่อที่จะสามารถใช้คำสั่ง npm test ใน Terminal ได้ เมื่อจะทำการทดสอบโดยใช้ Jest

จากนั้นสร้างไฟล์ app.js โดยมีโค้ดดังนี้

const express = require("express");
function makeApp(database){
    const app = express();
    app.use(express.json())
    app.post("/", async (req, res)=> {
        const {username, password} = req.body;
        if (!password || !username) {
            res.sendStatus(400)
            return
          }
        const newUser = await database.createUser(username,password);
        res.status(200).json(newUser)
    })
    return app
}

module.exports = makeApp

จากโค้ดใน app.js จะเป็นการสร้างฟังก์ชันชื่อ makeApp ซึ่งรับตัวแปรชื่อ database การเขียนในลักษณะนี้ทำให้สามารถเปลี่ยนฐานข้อมูลที่จะนำมาใช้กับ app ได้ง่าย ซึ่งจะทำให้สะดวกในการทำการทดสอบการทำงานของ API ของเรา

app ที่สร้างขึ้นภายในฟังก์ชันนี้ จะรอบรับวิธีการ POST ซึ่งต้องการข้อมูล username และ password โดยจะมีการตรวจสอบว่าผู้ใช้งานได้กรอกข้อมูล username และ password หรือไม่ หากขาดข้อมูลใดข้อมูลหนึ่งจะส่ง status code เป็น 400 กลับไป แต่หากมีข้อมูลครบถ้วน จะเรียกใช้ฟังก์ชัน createUser ของ database โดยส่งข้อมูล username และ password เข้าไป เพื่อบันทึกข้อมูลในฐานข้อมูล และจะส่ง Status Code เป็น 200 และ newUser ซึ่งเป็นข้อมูลที่ถูกส่งกลับมาจากฐานข้อมูลไปให้ผู้ใช้งาน

ในการใช้งานจริง database จะเป็นส่วนที่เกี่ยวข้องกับฐานข้อมูลที่ใช้งาน เช่นการเชื่อม การใช้ SQL เพื่อเพิ่มข้อมูลในตารางของฐานข้อมูล และการส่งข้อมูลจากฐานข้อมูลกลับไปที่ app ดังตัวอย่างโค้ดด้านล่างนี้

const {Pool} = require('pg')
const pool = new Pool({
    host: process.env.DB_HOST,
    user: process.env.DB_USER,
    password: process.env.DB_PASSWORD,
    port: 5432,
    database: process.env.DB_NAME
})

const createUser= (username,password)=>{
    query_createUser = 'INSERT INTO userstable (username, password)  VALUES($1, $2) returning *; '
    pool.query(query_createUser,[username,password],(error,results)=>{
        if(error) throw error;
        return results.rows[0]
    });
};

database = {};
database.createUser = createUser;
module.exports = database

จะเห็นว่าในโค้ดตัวอย่างนี้ database จะมีฟังก์ชันชื่อ createUser ซึ่งจะทำหน้าที่ในการนำข้อมูล username และ password ไปเพิ่มในฐานข้อมูล และส่งข้อมูลที่พึ่งเพิ่มกลับไปให้ app (ข้อมูลที่ส่งกลับไปให้ app นอกจาก username และ password ยังมี id ซึ่งเป็น Primary Key อีกด้วย) ซึ่งในบทความนี้ จะใช้ jest.fn() เพื่อสร้างฟังก์ชันจำลองแทนฟังก์ชัน createUser นี้

สร้างไฟล์สำหรับทดสอบ app.js ด้วย jest

สร้างไฟล์ app.test.js ที่ใช้ในการทดสอบการทำงานของ app.js จากนั้นเขียนโค้ดดังนี้

const request = require("supertest");
const makeApp = require("./app.js");
const mockCreateUser = jest.fn();
const mockDatabase = {};
mockDatabase.createUser = mockCreateUser;
const app = makeApp(mockDatabase);

โค้ดข้างต้นเป็นการเรียนใช้ supertest และฟังก์ชัน makeApp ที่เราพึ่งเขียนไว้ใน app.js จากนั้นสร้างฟังก์ชันจำลองชื่อ mockCreateUser โดยใช้ jest.fn() และสร้าง Object ชื่อ mockDatabase ซึ่งจะมี Property ชื่อ createUser ที่ตรงกับที่เราจะเรียกใช้ใน app.js

จากสร้าง Block สำหรับทำการตรวจสอบการทำงานของฟังก์ชัน createUser

test("should save the username and password to the database", async () => {
    const bodyData = { username: "username1", password: "password1"}
    const response = await request(app).post("/").send(bodyData);
  });

bodyData เป็นข้อมูลทดสอบและ response เป็นค่าที่ส่งกลับมาหาผู้ใช้งานจาก app ภายหลังผู้ใช้ลงทะเบียนใน app โดยในการทดสอบนี้ app จะเรียกใช้ฟังก์ชันจำลอง mockCreateUser

เราสามารถตรวจสอบได้ว่า username และ password ที่เราส่งเข้าไปใน app ผ่านเข้าไปยังฟังก์ชันถูกต้องหรือไม่ โดย

test("should save the username and password to the database", async () => {
    const bodyData = { username: "username1", password: "password1"}
    const response = await request(app).post("/").send(bodyData);
    expect(mockCreateUser.mock.calls[0][0]).toBe(bodyData.username);
    expect(mockCreateUser.mock.calls[0][1]).toBe(bodyData.password);
  });

mockCreateUser.mock.calls[0][0] คือ Parameter ลำดับที่ 1 ที่ส่งเข้าไปที่ฟังก์ชัน mockCreateUser เมื่อฟังก์ชันนี้ถูกเรียกครั้งแรก

mockCreateUser.mock.calls[0][1] คือ Parameter ลำดับที่ 2 ที่ส่งเข้าไปที่ฟังก์ชัน mockCreateUser เมื่อฟังก์ชันนี้ถูกเรียกครั้งแรก

หากเราลองทดสอบการทำงานโดยพิมพ์คำสั่ง npm test ใน terminal ควรจะได้ผลว่า PASS ดังนี้

สมมติว่าใน app.js เราลืมส่งค่า password เข้าไปในฟังก์ชัน createUser ดังนี้

เมื่อเราลองทำการทดสอบการทำงานอีกครั้ง ควรจะได้ผลเป็น Fail และมีข้อความแจ้งเตือนว่า ค่าที่ฟังก์ชันควรจะได้รับคือ “password1” แต่ส่งที่ส่งเข้าไปในฟังก์ชันกลับเป็น undefined ดังรูป

  ถัดมาเราจะตรวจสอบขั้นต่อไปว่า เมื่อฐานข้อมูลส่งข้อมูลกลับมา app สามารถรับข้อมูลและส่งกับไปให้ผู้ใช้งานได้อย่างถูกต้องหรือไม่ แต่เนื่องจากตอนนี้เราใช้ฟังก์ชันจำลอง ซึ่งไม่ได้เชื่อมต่อกับฐานข้อมูลจริงๆ ดังนั้นเราจะต้องเพิ่มโค้ดเพื่อให้ฟังก์ชันจำลอง mockCreateUser ส่งข้อมูล id  username และ password กลับมาเมื่อถูกเรียกใช้ เพื่อเลียนแบบการส่งข้อมูลกลับจากฐานข้อมูล โดย id กำหนดให้เป็น 1 ค่า ซึ่งในการทำงานจริงค่า id นี้ ฐานข้อมูลจะจัดการเพิ่มในตารางให้โดยอัตโนมัติ username และ password กำหนดให้มีค่าเหมือนกับ input ที่ส่งเข้าไปในฟังก์ชัน ดังนี้

ข้อมูลที่ผู้ใช้งานจะได้รับถูกเก็บไว้ใน response ซึ่งนอกจากจะมีค่า id  username และ password แล้ว ควรจะมีค่า status code เป็น 200 อีกด้วย ทำการเพิ่มการตรวจสอบได้ดังนี้

test("should save the username and password to the database", async () => {
    const bodyData = { username: "username1", password: "password1"}
    //ให้ mockCreateUser ส่งข้อมูล id usrname และ password กลับไปเมื่อถูกเรียกใช้งาน
    mockCreateUser.mockResolvedValue({ id: 1, ...bodyData });
    const response = await request(app).post("/").send(bodyData);
    expect(mockCreateUser.mock.calls[0][0]).toBe(bodyData.username);
    expect(mockCreateUser.mock.calls[0][1]).toBe(bodyData.password);
    expect(response.statusCode).toBe(200); // ตรวจสอบว่า status code = 200
    expect(response.body.id).toBe(1); // ตรวจสอบว่า id = 1
    expect(response.body.username).toBe(bodyData.username);
    expect(response.body.password).toBe(bodyData.password);
  });

เมื่อทดสอบการทำงานโดยพิมพ์คำสั่ง npm test ใน Terminal ควรจะได้ผลว่า PASS ดังนี้

สรุป

การใช้งาน Jest สามารถใช้ในการสร้างฟังก์ชันจำลองซึ่งสามารถตรวจสอบ Parameter ต่าง ๆ ที่ถูกส่งเข้าฟังก์ชันจำลองนี้ และยังสามารถกำหนดค่าตัวแปรต่าง ๆ ที่จะส่งออกมาเพื่อฟังก์ชันจำลองถูกใช้งาน ซึ่งคุณสมบัติเหล่านี้มีประโยชน์เป็นอย่างมากในการตรวจสอบการทำงานของ API แบบแยกส่วน ซึ่งจะง่ายต่อการหาสาเหตุเมื่อเกิดความผิดพลาดขึ้นในระบบ

อ้างอิง

  • https://www.borntodev.com/2022/06/24/%e0%b8%97%e0%b8%b3-unit-test-%e0%b8%9a%e0%b8%99-node-%e0%b8%94%e0%b9%89%e0%b8%a7%e0%b8%a2-jest/
  • https://jestjs.io/
sorajit.a

Author sorajit.a

More posts by sorajit.a

เราใช้คุกกี้เพื่อพัฒนาประสิทธิภาพ และประสบการณ์ที่ดีในการใช้เว็บไซต์ของคุณ คุณสามารถศึกษารายละเอียดได้ที่ นโยบายความเป็นส่วนตัว และสามารถจัดการความเป็นส่วนตัวเองได้ของคุณได้เองโดยคลิกที่ ตั้งค่า

ตั้งค่าความเป็นส่วนตัว

คุณสามารถเลือกการตั้งค่าคุกกี้โดยเปิด/ปิด คุกกี้ในแต่ละประเภทได้ตามความต้องการ ยกเว้น คุกกี้ที่จำเป็น

ยอมรับทั้งหมด
จัดการความเป็นส่วนตัว
  • คุกกี้ที่จำเป็น
    เปิดใช้งานตลอด

    ประเภทของคุกกี้มีความจำเป็นสำหรับการทำงานของเว็บไซต์ เพื่อให้คุณสามารถใช้ได้อย่างเป็นปกติ และเข้าชมเว็บไซต์ คุณไม่สามารถปิดการทำงานของคุกกี้นี้ในระบบเว็บไซต์ของเราได้
    รายละเอียดคุกกี้

  • คุกกี้สำหรับการติดตามทางการตลาด

    ประเภทของคุกกี้ที่มีความจำเป็นในการใช้งานเพื่อการวิเคราะห์ และ นำเสนอโปรโมชัน สินค้า รวมถึงหลักสูตรฟรี และ สิทธิพิเศษต่าง ๆ คุณสามารถเลือกปิดคุกกี้ประเภทนี้ได้โดยไม่ส่งผลต่อการทำงานหลัก เว้นแต่การนำเสนอโปรโมชันที่อาจไม่ตรงกับความต้องการ
    รายละเอียดคุกกี้

บันทึกการตั้งค่า