Unit test در جاوا ‌اسکریپت با jest - بخش دوم
ﺯﻣﺎﻥ ﻣﻄﺎﻟﻌﻪ: 6 دقیقه

Unit test در جاوا ‌اسکریپت با jest - بخش دوم

در بخش اول این مقاله ما به مباحث ابتدایی unit test در jest پرداختیم و برای آشنایی چند مثال ساده زدیم. اگر یک آشنایی اولیه با jest دارید نیازی به مطالعه بخش اول نیست ولی اگر تازه می‌خواهید شروع کنید پیشنهاد می‌کنم اول آن را مطالعه کنید.

همانطور که قول داده بودم در این مقاله به نوشتن unit test در نودجیاس خواهیم پرداخت. در اینجا من یک سرویس احراز هویت را مثال می‌زنم و شما با یاد گرفتن آن می‌توانید هر تستی را برای سرور نود خود بنویسید.

اول از همه بیاید درباره پکیج‌هایی که نیاز داریم بحث کنیم.

const { MongoMemoryServer } = require("mongodb-memory-server")

const app = require("../src/app")

const { internet } = require("faker")

const mongoose = require("mongoose")

const request = require("supertest")

پکیج mongodb-memory-server اطلاعات دیتابیس را روی رم ذخیره می‌کند که سرعت خواندن و نوشتن به نسبت بالاتر از دیتابیسی است که اطلاعات را رو هارد ذخیره می‌کند.

فایل app همان سرور اکسپرس ماست.

faker همانطور که از اسم این پکیج پیداست به شما کمک می‌کند تا اطلاعات فیک بسازید تا در تست خود استفاده کنید. البته می‌توانید این پکیج را نصب نکنید و اطلاعات را به صورت دستی برای هر تست بنویسید ولی من به شما پیشنهاد می‌دهم از این پکیج استفاده کنید چون این اطلاعات کاملا یونیک بوده، درست مانند زمانی است که شما به صورت دستی با مرورگر یا پست‌ من اطلاعات را وارد می‌کنید.

supertest با استفاده از این پکیج ما می‌توانیم درخواست‌های http برای تست به سرور خود ارسال کنیم.

قبل از اینکه بخواهیم شروع به تست نویسی کنیم باید یک سری تنظیمات روی پروژه خود اعمال کنیم. هدف از اعمال این تنظیمات انجام یک سری کار‌ها حین تست است به طور مثال: اتصال به دیتابیس، حذف رکورد‌های ثبت شده در هر درخواست و....

شروع کنید

پکیج‌های بالا و jest را نصب کنید البته در devDependencies. پس از نصب پکیج‌ها به فایل package.json بروید و در اینجا نیاز داریم دستور تست را به jest تغییر دهیم:

"scripts": {
	"test": "jest --watchAll --no-cache"
}

تنظیمات مربوط به jest:

"jest": {
	"testEnvironment": "node",
	"setupFilesAfterEnv": [
		"./tests/setup.js"
	]
}

از اینجا به بعد دیگر کاری با فایل package.json نداریم.

اگه به تنظیمات مربوط به jest نگاه کنید، setupFilesAfterEnv را می‌بینید که مسیر یک فایل را برایش ست کردیم خب این همان فایلی است که می‌خواهیم از ابتدا تا انتهای تست برای ما کار‌هایی را به صورت اتوماتیک انجام دهد.

به فولدر tests بروید و فایل setup.js را ایجاد کنید.

const { MongoMemoryServer } = require("mongodb-memory-server")

const app = require("../src/app")

const { internet } = require("faker")

const mongoose = require("mongoose")

const request = require("supertest")
let mongo
beforeAll(async () => {
	process.env.JWT_KEY = "jwtKey"

	mongo = new MongoMemoryServer()
	const mongoUri = await mongo.getUri()

	await mongoose.connect(mongoUri, {
		useNewUrlParser: true,
		useUnifiedTopology: true,
		useCreateIndex: true,
	})
})

beforeEach(async () => {
	const collections = await mongoose.connection.db.collections()

	for (let collection of collections) {
		await collection.deleteMany({})
	}
})

afterAll(async () => {
	await mongo.stop()
	await mongoose.connection.close()
})

قبل از اینکه بخواهم توضیحی درباره این کد‌ها بدهم لطفا با دقت نگاه کنید. در اینجا ما از beforeAll ، beforeEach و afterAll استفاده کردیم که همه اینها هوک یا به اصطلاح قلاب تست هستند. هوک beforeAll پیش از تست ارتباط ما با دیتابیس را برقرار می‌کند.beforeEach قبل هر تست همه اطلاعات درون دیتابیس را خالی می‌کند و afterAll بعد از اتمام تست این هوک اجرا می‌شود که وظیفه قطع ارتباط با دیتابیس را دارد.

توجه: JWT_KEY یک environment variables است که من آن را در beforeAll مقدار‌دهی کردم البته می‌توانید آن را با دستور تستی که در package.json نوشتید مقداردهی کنید به صورت زیر:

"scripts": {
	"test": "JWT_KEY=jwtKey jest --watchAll --no-cache"
}

تست نویسی

  • اعتبار سنجی اطلاعات ارسال شده برای ثبت نام کاربر
const app = require("../../src/app")

const request = require("supertest")
it("returns a 422 with missing email, username and password", async () => {
return request(app).post("/api/authentication/register").send({}).expect(422)
})

در اینجا یک فایل تست ایجاد کردیم و کد بالا را برای اطمینان از صحت اعتبار سنجی است. اگر کاربر هیچ داده‌ای برای ثبت نام نفرستد سرور کد ۴۲۲ را برمی‌گرداند و این تست کاملا درست است مگر اینکه کد بازگشتی از سرور ۴۲۲ نباشد.

پکیج faker به فایل خود اضافه کنید و ایمیل ، نام‌کاربری و رمز‌عبور تست بسازید:

const { internet } = require("faker")
// Seed data fake
let email = internet.email()
let password = internet.password(8)
let username = internet.userName()
  • ارسال ایمیل نادرست
it("returns a 422 with on invalid email", async () => {
	return request(app)
		.post("/api/authentication/register")
		.send({
			email: username,
			username,
			password,
		})
		.expect(422)
})

همانطور که مشاهده می‌کنید نام‌کاربری را به جای ایمیل معتبر ارسال کردیم پس به دلیل ارسال نادرست ایمیل سرور باید کد ۴۲۲ را به ما برگرداند.

  • ارسال رمز‌عبور غیرمعتبر
it("returns a 422 with on invalid password", async () => {
	return request(app)
		.post("/api/authentication/register")
		.send({
			email,
			username,
			password: password.slice(6),
		})
		.expect(422)
})

به طور معمول سرور رمز‌عبور کمتر از ۸ کاراکتر را قبول نمی‌کند ما در اینجا آمدیم سناریویی تعریف کردیم که اگر کاربر رمز‌عبور کمتر از ۸ کاراکتر را وارد کرد کد ۴۲۲ را دریافت کند.

  • ثبت نام موفقیت آمیز
it("returns a 201 on successful signup", async () => {
	return request(app)
		.post("/api/authentication/register")
		.send({
			email,
			username,
			password,
		})
		.expect(201)
})

در این تست همه اطلاعات به صورت صحیح ارسال شده است پس انتظار می‌رود کد ۲۰۱ یعنی ثبت نام با‌موفقیت انجام شده است از طرف سرور به ما برگشت داده شود.

  • جلوگیری از ثبت ایمیل تکراری
it("disallows duplicate emails", async () => {
	await request(app)
		.post("/api/authentication/register")
		.send({
			email,
			username,
			password,
		})
		.expect(201)

	await request(app)
		.post("/api/authentication/register")
		.send({
			email,
			username,
			password,
		})
		.expect(400)
})

سناریو ما در اینجا این است که وقتی کاربر با یک ایمیل ثبت نام کرد دیگر نمی‌تواند با همان ایمیل اقدام به ثبت نام نماید و اگر کاربری با همان ایمیل در سیستم وجود داشته باشد کد ۴۰۰ بازگردانده می‌شود.

بیایید یکم تست‌های پیچیده‌تر بنویسیم

شاید به این فکر کرده باشید که اگر بخواهم برای بعضی apiها که نیاز دارند تا کاربر حتما لاگین شده باشد تکرار کد شما در تست بالا می‌رود ولی اینطور نیست شما می‌توانید تابع سراسری تعریف کنید و از آن در بقیه تست‌های خود استفاده کنید. به فایل setup.js برگردید.

global.authorization = async (
	email = internet.email(),
	password = internet.password(),
	username = internet.userName()
) => {
	await request(app).post("/api/authentication/register").send({
		email,
		password,
		username,
	})

	await mongoose.connection
		.collection("users")
		.updateOne(
			{ email: email.toLowerCase(), username: username.toLowerCase() },
			{ $set: { status: "active" } }
		)

	const response = await request(app).post("/api/authentication/login").send({
		email,
		password,
	})

	expect(response.status).toBe(200)
	return response.get("Set-Cookie")
}

این تابع سراسری api لاگین را برای ما شبیه سازی می‌کند به این صورت که در اول ثبت نام انجام می‌شود بعد از آن برای پیدا کردن کاربر ایجاد شده از، ایمیل و نام‌کاربری آن برای جستجو استفاده می‌کنیم تا پس یافتن آن، وضعیت کاربر را از inactive به active تغییر دهیم سپس با ارسال ایمیل و رمز‌عبور به api لاگین و موفقیت آمیز بودن آن کوکی را برگشت می‌دهیم.

حال شما می‌توانید با استفاده از این تابع تست‌هایی که نیاز به ورود کاربر دارند را بنویسید مثل گرفتن اطلاعات کاربر لاگین شده.

it("returns a 401 when authorization empty or null", async () => {
	await request(app).get("/api/authentication/user").expect(401)
	await request(app)
		.get("/api/authentication/user")
		.set("Cookie", "")
		.expect(401)
	await request(app)
		.get("/api/authentication/user")
		.set("Cookie", [])
		.expect(401)
})

این سناریو زمانی که کاربر لاگین نکرده باشد یا به عبارتی دیگر کوکی کاربری که درخواست می‌دهد خالی است که در این صورت سرور کد ۴۰۱ که یعنی درخواست غیر مجاز است را می‌دهد.

it("returns a 200 on successful current client", async () => {
	const cookie = await global.authorization()
	const response = await request(app)
		.get("/api/authentication/user")
		.set("Cookie", cookie)
		.expect(200)
	expect(response.body.data).toBeDefined()
})

در این تست از تابع سراسری که برای لاگین نوشتیم استفاده کردیم به گونه‌ای که اول لاگین توسط تابع انجام می‌شود سپس کوکی ساخته شده توسط سرور برگشت داده می‌شود و ما با ارسال آن به api یوزر می‌توانیم اطلاعات کاربر را مشاهده کنیم.

امیدوار که این مقاله براتون مفید بوده باشه.

چه امتیازی برای این مقاله میدهید؟

خیلی بد
بد
متوسط
خوب
عالی
4 از 1 رای

/@mohamadresaaa
محمدرضا مصلی
توسعه دهنده وب

حدود ۶ سالی هست که دارم برنامه نویسی میکنم و به دلیل علاقه زیادی که به زبان جاوا اسکریپت داشتم، به سمت تکنولوژی nodejs و فریم ورک های آن رفتم و همچنان در این حوزه فعالیت میکنم و دوست دارم تجربه خودم را با دیگران به اشتراک بگذارم.

دیدگاه و پرسش

برای ارسال دیدگاه لازم است وارد شده یا ثبت‌نام کنید ورود یا ثبت‌نام

در حال دریافت نظرات از سرور، لطفا منتظر بمانید

در حال دریافت نظرات از سرور، لطفا منتظر بمانید