Vitest کا استعمال کرتے ہوئے ایکسپریس میں خودکار جانچ کیسے کریں۔

ایپلیکیشن انضمام کو جانچنے کے لیے ٹیبز کو مسلسل تبدیل کرتے ہوئے API منطق کے ذریعے سوچنا بوجھل اور وقت طلب ہو سکتا ہے۔

ایپلیکیشن ٹیسٹ لکھ کر وقت اور توانائی کی بچت کریں جو ہر بار جب آپ ترقی کے دوران IDE کو چھوڑے بغیر کوئی نئی خصوصیت شامل کرتے ہیں تو چلتے ہیں۔ اس سے آپ کو یہ یقینی بنانے میں مدد ملے گی کہ ہر خصوصیت توقع کے مطابق کام کرتی ہے۔

اس گائیڈ میں، ہم آپ کو کوڈ کے ساتھ اعتماد پیدا کرنے میں مدد کریں گے۔ آپ یہ سیکھیں گے کہ پہلے ٹیسٹ کے ساتھ اپنے API کی توثیق کیسے کی جائے اور پھر پوسٹ مین یا دوسرے API ٹیسٹنگ ٹول میں نتائج دیکھیں۔

شرطیں

  • Node.js اور ایکسپریس کی بنیادی باتیں: آپ کو Node.js اور Express (یا اسی طرح کی لائبریریوں جیسے: fastify)، بشمول ایک سادہ API بنانے اور چلانے کا طریقہ۔

  • ٹائپ اسکرپٹ کا کام کا علم: اس گائیڈ میں کوڈ کے ٹکڑوں کو TypeScript کا استعمال کرتے ہوئے لکھا گیا ہے، لہذا آپ کو TypeScript کی بنیادی باتوں کی ٹھوس سمجھ ہونی چاہیے۔

  • MongoDB کا بنیادی علم: یہ مددگار ہے، لیکن ضروری نہیں ہے۔ اس گائیڈ میں دی گئی مثالیں مظاہرے کے مقاصد کے لیے MongoDB کا استعمال کرتی ہیں، لیکن بنیادی منطق کسی بھی ڈیٹا بیس یا ڈیٹا لیئر پر لاگو ہوتی ہے۔ صرف ٹولنگ مختلف ہے۔

  • API ہینڈ آن تجربہ: ایکسپریس کا استعمال کرتے ہوئے کم از کم ایک بیک اینڈ API لکھنے کا تجربہ آپ کو زیادہ مؤثر طریقے سے پیروی کرنے میں مدد کرے گا۔

  • تجسس اور ترغیب: APIs کے لیے ٹیسٹ لکھنے اور چلانے کا طریقہ سیکھ کر اپنی پسدید کی مہارتوں کو گہرا کرنے کی خواہش۔

  • ترجیحات: Node.js ورژن 20 یا اس سے زیادہ یہ آپ کے کمپیوٹر پر انسٹال ہے۔

انڈیکس

آپ اس گٹ ریپوزٹری میں اس گائیڈ میں استعمال ہونے والے تمام کوڈ کے ٹکڑوں کو تلاش کر سکتے ہیں۔ ہر مثال کی اپنی شاخ ہوتی ہے اور اگر آپ مکمل مثال کا ورژن چاہتے ہیں تو اسے ایک مرکزی شاخ میں جوڑ دیا جاتا ہے۔ اگر آپ کو یہ کارآمد معلوم ہوا، تو براہ کرم ذخیرے پر ستارہ لگائیں۔

ٹیسٹ کے کلیدی تصورات

جانچ آپ کو اپنے کوڈ بیس میں اعتماد پیدا کرنے میں مدد کرتی ہے۔ آپ اپنی درخواست میں دوسرے کوڈ کی جانچ کرنے کے لیے کوڈ لکھتے ہیں، یا اس بات کی تصدیق کرنے کے لیے کہ کوئی فنکشن یا فعالیت توقع کے مطابق کام کرتی ہے۔

یہ گائیڈ دو قسم کے ٹیسٹوں کا احاطہ کرتا ہے:

  • یونٹ ٹیسٹنگ: یہ ٹیسٹ کی سب سے بنیادی قسم ہے اور، میری رائے میں، سب سے آسان۔ یہ دیکھنے کے لیے کہ آپ کا کوڈ کیسا کارکردگی دکھاتا ہے، انفرادی طور پر کوڈ کے ایک ٹکڑوں کی جانچ کریں۔

  • انضمام کی جانچ: اس نقطہ نظر کو جانچنے کے لیے استعمال کیا جاتا ہے کہ کس طرح ایپلی کیشن کے مختلف حصوں کو مربوط کیا جاتا ہے اور حسب منشا ایک ساتھ کام کرتے ہیں۔

ٹیسٹ کی کلیدی شرائط

اس گائیڈ کے دوران، ہم جانچ سے متعلق کچھ تکنیکی اصطلاحات استعمال کریں گے جن کی ہم پہلے وضاحت کرنا چاہیں گے۔

  • طنز: موکنگ ایک ایسی تکنیک ہے جو حقیقی فنکشن کالز کو جعلی لاگو کرنے کے لیے استعمال ہوتی ہے۔

  • نگرانی: دیکھنا کسی فنکشن کو دیکھنے کا ایک طریقہ ہے، مثال کے طور پر، آیا فنکشن کو کسی خاص قسم کی دلیل کے ساتھ بلایا گیا تھا یا اسے کتنی بار بلایا گیا تھا۔

  • تضاد: دعوے اس بات کی تصدیق کرتے ہیں کہ آپ کو جو آؤٹ پٹ مل رہا ہے وہ متوقع آؤٹ پٹ سے میل کھاتا ہے۔

اب جبکہ ہم نے اس کا احاطہ کر لیا ہے، آئیے ٹیسٹنگ کی کچھ بنیادی باتوں پر غور کریں تاکہ آپ ٹیسٹ لکھنا شروع کرنے سے پہلے اپنے آپ کو بہترین طریقوں سے آشنا کر سکیں۔

اپنی ٹیسٹ فائل کا نام کیسے رکھیں

اس بات کو یقینی بنائیں کہ آپ کی ٹیسٹ فائلیں درج ذیل میں سے کسی ایک نام کے کنونشن کی پیروی کرتی ہیں:

  • filename.test.ts

  • filename.spec.ts

  • filename.test.js

  • filename.spec.js

دونوں spec اور test آپ فائل کے نام میں مطلوبہ الفاظ شامل کرکے ٹیسٹ فائل چلا سکتے ہیں۔ یہ جانچ کے فریم ورک کو درست فائلوں کو انجام دینے میں بھی مدد کرتا ہے۔

آپ اپنے ٹیسٹ لکھنے کے لیے جو زبان استعمال کرتے ہیں اس پر منحصر ہے، آپ فائل میں ٹائپ اسکرپٹ یا جاوا اسکرپٹ ایکسٹینشنز میں سے کسی ایک کا انتخاب کرسکتے ہیں۔ یہ گائیڈ TypeScript استعمال کرتا ہے، لہذا TypeScript (.ts) ٹیسٹ فائل ورژن۔

ٹیسٹ فائل کا حصہ

عام طور پر، ٹیسٹ فائل کے چار اہم حصے ہوتے ہیں جن سے آپ کو واقف ہونے کی ضرورت ہوگی:

  • describe: لکھے جانے والے ٹیسٹ کو بیان کرنے کے لیے استعمال کیا جاتا ہے۔

  • it: ان شرائط کی وضاحت کرنے کے لیے استعمال کیا جاتا ہے جو ٹیسٹ کرتے وقت فنکشن کو پاس کرنا ضروری ہے۔

  • expect: ٹیسٹ کے تحت فنکشن کو کال کرتے وقت متوقع نتیجہ کی قسم کا تعین کرنے کے لیے استعمال کیا جاتا ہے۔

  • matchers: یہ طریقہ درج ذیل ہے۔ expect ایک کلیدی لفظ اس بات کا اندازہ کرنے کے لیے استعمال کیا جاتا ہے کہ آیا ٹیسٹ کے تحت فنکشن ایسی قدر لوٹاتا ہے جو متوقع ڈیٹا یا اقدار کو پورا کرتا ہے۔

ٹیسٹ فائل کی مثال:

import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () => {
  it("must define email validation function", () => {
    expect(validateEmail).toBeDefined();
  });
});

اوپر کا ٹکڑا ہے۔ validateEmail فنکشن کی وضاحت کی گئی ہے۔

اس ٹیسٹ فائل میں:

  • ہم ہیں describe ٹیسٹ کی تفصیل بتاتا ہے۔ describe یہ مختلف ٹیسٹ کیسز کو سنبھالنے کے لیے ٹیسٹ اور افعال کی تفصیل لیتا ہے۔

  • کہ it مطلوبہ الفاظ ٹیسٹ کے تحت فنکشن کے لیے ٹیسٹ کی وضاحت اور وضاحت کرتے ہیں۔ it ٹیسٹ کے دعوے کو انجام دینے اور اس پر کارروائی کرنے کے لیے یہ مخصوص ٹیسٹ کی تفصیل اور کال بیک فنکشن کو بیان کرنے والی ایک تار لیتا ہے۔

  • پھر expect یہ چیک کرنے کے لیے کہ آیا کچھ شرائط مماثل ہیں فنکشن کی واپسی کی اقسام کا استعمال کریں۔ toBeDefined ملاپ کا طریقہ۔

میچوں کی فہرست:

بہت سے میچرز ہیں جو آپ استعمال کر سکتے ہیں۔ ان میں سے چند یہ ہیں:

  • toBe: پاس شدہ قدر کا موازنہ یہ دیکھنے کے لیے کرتا ہے کہ آیا یہ فنکشن کی واپسی کی قیمت سے مماثل ہے۔ بنیادی ڈیٹا کی اقسام جیسے تار، نمبر وغیرہ کے لیے استعمال کیا جاتا ہے۔

  • toEqual: پاس شدہ قدر کا موازنہ یہ دیکھنے کے لیے کرتا ہے کہ آیا یہ فنکشن کی واپسی کی قیمت سے مماثل ہے۔ غیر قدیم ڈیٹا کی اقسام کے لیے استعمال کیا جاتا ہے جیسے کہ اشیاء، صفوں وغیرہ۔

  • toThrow: اس فنکشن پر استعمال کیا جاتا ہے جس نے یہ تعین کرنے کے لیے ایک خامی پیدا کی کہ آیا فنکشن نے متوقع ایرر آبجیکٹ یا مثال کو اٹھایا ہے۔

  • toBeCalledWith: یہ چیک کرنے کے لیے استعمال کیا جاتا ہے کہ آیا کسی فنکشن کو دیے گئے پیرامیٹرز کے ساتھ بلایا گیا ہے۔

  • toBecalledOnce: اس بات کو یقینی بنانے کے لیے استعمال کیا جاتا ہے کہ فنکشن صرف ایک بار بلایا جائے۔

  • toBeDefined: یہ چیک کرنے کے لیے استعمال کیا جاتا ہے کہ آیا کسی فنکشن کی تعریف کی گئی ہے۔

  • toBeUndefined: یہ چیک کرنے کے لیے استعمال کیا جاتا ہے کہ آیا کوئی فنکشن غیر متعینہ قدر لوٹاتا ہے۔

  • toBeTruthy: یہ چیک کرنے کے لیے استعمال کیا جاتا ہے کہ آیا کوئی فنکشن ایک حقیقی بولین ویلیو واپس کرتا ہے۔

  • toBeFalsy: یہ چیک کرنے کے لیے استعمال کیا جاتا ہے کہ آیا کوئی فنکشن غلط بولین ویلیو لوٹاتا ہے۔

یہ وہاں کے بہت سے میچرز میں سے صرف چند ہیں۔

غیر ٹیسٹ کی تنصیب

اب جب کہ آپ کو جانچ کی کچھ بنیادی باتیں معلوم ہیں، آپ حقیقی جانچ کرنا شروع کر سکتے ہیں۔ آئیے انسٹالیشن کے ساتھ شروع کریں۔ vitestآپ کی درخواست کے لیے ٹیسٹ چلانے اور لکھنے کے لیے استعمال کرنے کے لیے ایک فریم ورک۔

آپ انسٹالیشن کے لیے استعمال کرنے کے لیے ڈیفالٹ پیکیج مینیجر کا انتخاب کر سکتے ہیں۔ vitest نیچے دی گئی فہرست سے:

pnpm add -D vitest # for pnpm package manager
npm install -D vitest # for npm package manager
yarn add -D vitest # for yarn package manager
bun add -D vitest # for bun package manager

یونٹ ٹیسٹنگ

یونٹ ٹیسٹنگ ایپلی کیشن میں کوڈ کے چھوٹے، الگ تھلگ ٹکڑوں کی جانچ پر توجہ مرکوز کرتی ہے۔ ایک سادہ مثال یہ ہو گی کہ اگر آپ کے پاس ڈیٹا بیس میں رابطے کی معلومات شامل کرنے کی صلاحیت ہے۔ جانچ کے مقاصد کے لیے، آپ اپنے ڈیٹا بیس میں رابطہ شامل کرنے سے پہلے چیک کر سکتے ہیں کہ آیا ای میل درست ہے۔

آئیے ایک سادہ ای میل کی توثیق کے فنکشن کی جانچ کرکے شروع کریں تاکہ آپ اس بات سے واقف ہو سکیں کہ یونٹ ٹیسٹ کیسے کام کرتے ہیں اور انہیں کیسے لکھنا ہے۔

export default function validateEmail(email: string) {
  const regex = /^[^s@]+@[^s@]+.[^s@]+$/;

  if (regex.test(email)) {
    return true;
  } else {
    throw new Error("Invalid email format");
  }
}

اوپر کا ٹکڑا ایک فنکشن کو خارج کرتا ہے جو ای میل وصول کرتا ہے اور پھر یہ چیک کرنے کے لیے کہ آیا ای میل درست ہے ایک ریگولر ایکسپریشن استعمال کرتا ہے۔ اگر ای میل غلط ہے تو، ایک خرابی واقع ہوگی۔

کے لیے ٹیسٹ: validateEmail فنکشن

آئیے اس بات کو یقینی بناتے ہوئے شروع کریں کہ: validateEmail درست ای میلز کے لیے صحیح لوٹاتا ہے۔

import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () => {

  it("returns true for valid email", () => {
    const sampleEmail = "arnoldjabo@gmail.com";
    expect(validateEmail(sampleEmail)).toBeTruthy();
  });

});

مندرجہ بالا ٹیسٹ ایک متغیر پیدا کرتا ہے۔ sampleEmail اسے بطور نمونہ ای میل استعمال کیا جائے گا۔ validateEmail فنکشن اسے محفوظ کریں اور چلائیں۔ npx vitest آپ کے ٹرمینل میں۔ آپ کو ایک ٹرمینل نظر آئے گا جو ٹیسٹ کے نتائج دکھا رہا ہے۔ یہ نیچے اسکرین شاٹ کی طرح نظر آنا چاہئے۔

جب آپ ٹیسٹ چلاتے ہیں، تو Vitest ٹیسٹ کی جانے والی ٹیسٹ فائلوں کی فہرست دکھاتا ہے، چلائے جانے والے ٹیسٹوں کی تعداد، اور پاس شدہ اور ناکام ٹیسٹوں کی تعداد۔

آئیے غلط ای میلز کا پتہ لگانے کے لیے ایک اور ٹیسٹ بنائیں۔

import { describe, it, expect } from "vitest";
import validateEmail from "../utils/email-validation";

describe("email validation test suites", () => {

    it("throws error for invalid email", () => {
    const sampleEmail = "verymasd.com";
    const invalidEmailResults = () => validateEmail(sampleEmail);
    expect(invalidEmailResults).toThrow("Invalid email format");
  });

});

ایسے فنکشنز کے لیے جو غلطی پیدا کرتے ہیں، آپ کو اس فنکشن کو کسی دوسرے فنکشن کے اندر لپیٹنا چاہیے تاکہ ٹیسٹ کو دعویٰ تک پہنچنے سے پہلے رکنے سے روکا جا سکے۔ expect پارٹ ٹائم نوکری.

اوپر کی مثال میں validateEmail فنکشن کو ایک اور فنکشن کے اندر لپیٹ دیا گیا ہے جس میں جو بھی خرابیاں ہوں گی اسے روکے گا۔ validateEmail یہ ایک پھینک ہے. پھر یہ invalidEmailResults متغیر اگلا ہم toThrow مماثل شخص expect دعوی سے مماثل غلطی کی قسم validateEmail ایک خراب ای میل کی وجہ سے پھینکے جانے کی توقع ہے۔

جب ہم ٹیسٹ چلاتے ہیں، اب ہم دیکھتے ہیں کہ دونوں ٹیسٹ پاس ہوتے ہیں۔

غلط ای میلز کے لیے ٹیسٹ پاس کیا، جس سے غلط ای میلز کے لیے غلطی ہوتی ہے۔

اگر اسے پیک نہیں کیا گیا تھا۔ validateEmail اگر آپ کسی دوسرے فنکشن کے اندر فنکشن چلا رہے ہیں جب خرابی واقع ہوتی ہے، تو آپ کو اپنے ٹیسٹ چلانے پر کچھ ایسا نظر آئے گا:

یہ وہ ایرر میسج ہے جو آپ کو اپنے ٹیسٹ ٹرمینل میں موصول ہوگا اگر آپ اس فنکشن کو نہیں لپیٹتے ہیں جو کسی دوسرے فنکشن میں غلطی کو بڑھاتا ہے۔

جیسا کہ آپ دیکھ سکتے ہیں، غلطی ٹیسٹ کے حتمی دعوے تک پہنچنے سے پہلے ہوتی ہے۔ لہذا، اگر آپ کے پاس کوئی ایسا فنکشن ہے جو ایرر پھینکتا ہے، تو اسے دوسرے فنکشن کے اندر لپیٹنا نہ بھولیں تاکہ ٹیسٹنگ کے دوران ایرر پھینکنے سے بچا جا سکے۔

ٹپ: یونٹ ٹیسٹ لکھتے وقت، متوقع فنکشن ان پٹ اور آؤٹ پٹس پر توجہ دیں۔ آپ کو فنکشن کے نفاذ کے بارے میں فکر کرنے یا سوچنے کی ضرورت نہیں ہے۔ ان پٹ اور آؤٹ پٹ یہاں کلیدی عناصر ہیں۔

API کال ٹیسٹنگ

اب آپ کو سمجھنا چاہیے کہ یونٹ ٹیسٹ کیسے کام کرتے ہیں۔ اب آئیے دیکھتے ہیں کہ ڈیٹا بیس کو کال کرنے والے API کو یونٹ ٹیسٹ کرنے کا طریقہ۔

import Contacts from "../../schema/contactList";
import { Request, Response } from "express";
import validateEmail from "../../utils/email-validation";

async function addContacts(req: Request, res: Response) {
  const { contactName, phoneNumber, email } = req.body;
  validateEmail(email);

  const contact = await Contacts.create({ contactName, phoneNumber, email });

  return res.status(201).json({ message: `successfully created ${contact.contactName}` });
}

export default addContacts;

اوپر کوڈ کے ٹکڑوں میں ایک فنکشن ہے جو ڈیٹا بیس کے ساتھ رابطہ پیدا کرتا ہے۔ یہ بھی تصدیق کرتا ہے کہ آگے بھیجی گئی ای میل درست ہے۔

یہ مثال ڈیٹا بیس کے لیے منگو ڈی بی کا استعمال کرتی ہے اور کوڈ بیس کو منگو ڈی بی مثال کے ساتھ مربوط کرنے کے لیے مونگوز۔

غیر خالص فنکشنز (یعنی وہ فنکشنز جو بیرونی خدمات پر منحصر ہوتے ہیں، جیسے API کالز یا دیگر فنکشنز پر کالز) کے یونٹ ٹیسٹس کو قدرے مختلف طریقے سے جانچا جاتا ہے۔

ان صورتوں میں، آپ اصل فنکشن کا ایک فرضی یا جعلی ورژن بناتے ہیں جو ایکسٹرنل سروس کو کال کرتا ہے، اور پھر اس کے رویے کی وضاحت کرتے ہیں کہ اس واپسی کی قیمت سے مماثل ہو جس کی آپ توقع کریں گے اگر آپ حقیقی ورژن استعمال کرتے ہیں۔

آئیے ای میل کی تصدیق کی خصوصیت کے نفاذ کا مذاق اڑاتے ہوئے شروع کریں۔ اس سے آپ کی سمجھ کو مستحکم کرنے میں مدد ملے گی کہ عام طور پر یونٹ ٹیسٹوں اور ٹیسٹوں میں موکس کیسے کام کرتے ہیں۔

میمو: طنز کرنا validateEmail فعالیت یہاں اتنی اہم نہیں ہے کیونکہ ای میل کی تصدیق کی خصوصیت کو خود استعمال کرنے سے زیادہ فرق نہیں ہے۔ تاہم، سیکھنے کے مقاصد کے لیے، آئیے آپ کو یہ سمجھنے میں مدد کے لیے ایک ماک اپ بنائیں کہ یہ کیسے کام کرتا ہے۔

ایک ماڈیول درآمد کا مذاق اڑاتے وقت ہمارے پاس ہے۔ vi.mock ایک فنکشن جو دیے گئے ماڈیول کی درآمدات کو اصل ماڈیول کے ڈمی یا جعلی ورژن میں تبدیل کرنے میں مدد کرتا ہے۔

vi.mock(filepath,callback);

vi.mock یہ دو دلائل لیتا ہے: ماڈیول کی فائل پاتھ لوکیشن جس کا آپ مذاق اڑانا چاہتے ہیں، اور ایک کال بیک فنکشن جسے فیکٹری فنکشن کہا جاتا ہے جو ماڈیول کی درآمد کو ایک فرضی شکل میں تبدیل کرنے کے لیے استعمال کیا جائے گا۔

آئیے مذاق کے ساتھ شروع کرتے ہیں۔ validateEmail ایک فیکٹری فنکشن بنائیں جو ایک راستہ بناتا ہے اور ماڈیول کو ایک موک اپ میں تبدیل کرتا ہے۔

vi.mock("../utils/email-validation", () => {
  return { default: vi.fn()};
});
import validateEmail from "../utils/email-validation";

کال بیک فنکشن (فیکٹری فنکشن) میں، برآمد شدہ ماڈیول سے کسی چیز کو درج ذیل کیز کے ساتھ واپس کریں: بنیادی اور قدر vi.fn. فنکشن کا مذاق اڑانے کے لیے ہم استعمال کرتے ہیں: vi.fn() یہ خود بخود فنکشن کی ریٹرن ویلیو کو اس کے ساتھ بدل دیتا ہے: undefined.

ہم پہلے سے طے شدہ اقدار کو بطور کلید استعمال کرتے ہیں۔ validateEmail افعال پہلے سے طے شدہ برآمد کے ساتھ برآمد کیے جاتے ہیں۔ اگر یہ ایک نامزد برآمد تھا، تو واپسی آبجیکٹ ڈیفالٹ کے بجائے اصل برآمد کا نام استعمال کرتا۔

vi.mock("../utils/email-validation", () => {
  return { validateEmail: vi.fn() };
});
import { validateEmail } from "../utils/email-validation";

اگر آپ اصلی ماڈیول استعمال نہیں کرنا چاہتے ہیں، تو ہمیشہ فرضی ماڈیول کے ساتھ کام کرنے کے بعد ماڈیول درآمد کریں۔

ایک طریقہ ہے vi.fn() یہ فرضی فنکشن کے نفاذ اور طرز عمل کی وضاحت میں مدد کرتا ہے۔ ان طریقوں میں سے کچھ یہ ہیں:

  • mockReturnValue: ایک فرضی فنکشن کے لیے واپسی کی قدر کی وضاحت کے لیے استعمال کیا جاتا ہے۔

  • mockRejectsValue: وعدہ پر مبنی فنکشنز میں استعمال کیا جاتا ہے تاکہ فنکشن واپس آئے گا غلطی کی وضاحت کریں۔

  • mockResolveValue: وعدہ پر مبنی فنکشنز میں استعمال کیا جاتا ہے اس بات کی وضاحت کرنے کے لیے کہ فنکشن کون سا ڈیٹا واپس کرے گا۔

  • mockImplementation: فرضی فنکشن کے نئے فنکشن رویے کی وضاحت کرنے کے لیے استعمال کیا جاتا ہے۔

  • mockReturnThis: فنکشن کا مذاق اڑانے کی اصل مثال واپس کرنے کے لیے استعمال کیا جاتا ہے۔

یہ وہ طریقہ ہے جسے آپ زیادہ تر وقت استعمال کریں گے جب آپ اپنے فرضی نفاذ کی وضاحت کرتے ہیں اور فرضی کی واپسی کی قیمت کو ترتیب دیتے ہیں۔ یاد رہے کہ اور بھی بہت سی چیزیں ہیں۔

یہاں emailValidateہم ہیں mockReturnValue اور mockImplementation طریقے

آئیے اسے استعمال کریں۔ mockReturnValue verifyEmail فنکشن کو بطور ڈیفالٹ درست کرنے کے لیے، یہ فرض کرتے ہوئے کہ ای میل فارمیٹ درست ہے، درج ذیل کام کریں:

vi.mock("../utils/email-validation", () => {
  return { default: vi.fn().mockReturnValue(true) };
});
import validateEmail from "../utils/email-validation";

ہم فرضی فنکشن کی وضاحت اس طرح کرتے ہیں: vi.fn پھر زنجیر جوڑیں۔ mockReturnValue(true) ہمارے معاملے میں ہم موک فنکشن ڈیفالٹ ریٹرن ویلیو (غیر متعینہ) کو سچ میں تبدیل کرتے ہیں۔

فرضی نفاذ کی تعریف

آپ فرضی فنکشن کے ساتھ بہت سی چیزیں کر سکتے ہیں، جیسے کہ فرضی فنکشن کے لیے ایک نئے نفاذ کی وضاحت کرنا جو اصل فنکشن کی موجودہ منطق کی جگہ لے لے۔

آئیے ایک جعلی ای میل کے ساتھ ایک ٹیسٹ سویٹ بنائیں جو رابطہ بناتے وقت غلط ای میل کو آگے بھیجنے پر غلطی کرے گا۔

import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";
import addContacts from "../src/controllers/add-contacts.controller";

import { type Response, type Request } from "express";

vi.mock("../utils/email-validation", () => {
  return { default: vi.fn().mockReturnValue(true) };
});

import validateEmail from "../utils/email-validation";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () => {
  it("throws error for the wrong email address", async () => {
    const req = {
      body: { ...fakeContact, email: "fakemail" },
    } as Request;  

    const res = {
      status: vi.fn().mockReturnThis(),
      json: vi.fn(),
    } as any as Response;

    (validateEmail as ReturnType).mockImplementation(() => {
      throw new Error("invalid email!");
    });
 
    await expect(addContacts(req, res)).rejects.toThrow();
  });
});

مندرجہ بالا ٹکڑا ایک جعلی درخواست آبجیکٹ کا مذاق اڑاتا ہے یا تخلیق کرتا ہے جو درج ذیل درخواست کی قسم پر ڈالا جاتا ہے: express. جوابی اعتراض کے ساتھ بھی ایسا ہی کریں۔ تاہم، یہاں فرق یہ ہے کہ جوابات کا استعمال ایکسپریس ردعمل (ریاست اور ردعمل) میں استعمال کیے جانے والے عام طریقوں کے لیے بھی مذاق بناتا ہے۔ json اعتراض)۔

پھر واپسی کی قسم کو تبدیل کریں: validateEmail TypeScript انتباہات سے بچنے کے لیے اپنے فنکشن کو وائٹسٹ موک فنکشن کی قسم میں شامل کریں۔ پھر ہم mockImplementation ایک نئی غلطی کو اندر کیسے پھینکنا ہے۔ validateEmail.

اب جب کہ وعدے پر مبنی غلطیاں ہوتی ہیں، دعوے مختلف طریقے سے کام کرتے ہیں۔ ہم ہیں rejects آپ ایک دعویٰ لاگو کرتے ہیں اور پھر ایک اور میچر منسلک کرتے ہیں جو اس بات کو متحرک کرتا ہے کہ فنکشن کس قسم کی خرابی پیدا کرے گا۔

ٹپ: TypeScript کے ساتھ کام کرتے وقت، آپ رسپانس آبجیکٹ کو کاسٹ نہیں کر سکتے جیسا کہ آپ نے درخواست آبجیکٹ کے ساتھ کیا تھا کیونکہ رسپانس آبجیکٹ درخواست سے کہیں زیادہ سخت ہے۔ لہذا آپ کو پہلے اسے کسی پر کاسٹ کرنے کی ضرورت ہے اور پھر اسے جوابی آبجیکٹ پر واپس کاسٹ کرنا ہوگا۔ یہ آپ کو TypeScript وارننگز سے بچنے کی اجازت دیتا ہے جبکہ آپ کے ٹیسٹ کوڈ کے لیے اقسام کو محفوظ رکھتے ہیں۔

منگوز ماڈل موک

یونٹ ٹیسٹ میں، آپ ٹیسٹ ڈیٹا کو حقیقی ڈیٹا بیس میں اسٹور نہیں کرنا چاہتے۔ اس کے بجائے ہم ایک سروس کا نفاذ بنا سکتے ہیں جو ڈیٹا بیس سروس کو کال کرتی ہے۔ ہمارے معاملے میں create منگوز کا طریقہ۔ منگو ڈیٹا بیس میں ریکارڈز کو اسٹور کریں۔ اس کے بعد آپ اس بات کی وضاحت کر سکتے ہیں کہ کامیابی پر کیا واپس کیا جانا چاہیے (یہ وہی نظر آنا چاہیے جو آپ واپس کریں گے اگر آپ حقیقی ڈیٹا بیس استعمال کر رہے ہوں)۔

آئیے منگوز ماڈل کا مذاق اڑانے کے لیے لائبریریوں کو انسٹال کرکے شروع کریں۔ mockingoose:

pnpm add -D mockingoose # for pnpm package manager
npm install -D mockingoose # for npm package manager
yarn add -D mockingoose # for yarn package manager
bun add -D mockingoose # for bun package manager

تنصیب کے بعد، اپنے رابطہ ماڈل کے لیے ایک موک اپ بنائیں۔

import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () => {
  it("successfully create a new contact to the database", async () => {
    mockinggoose(Contacts).toReturn(fakeContact, "save");
  });

});

منگوز ماڈل کا مذاق اڑانے کے لیے، ہم کہتے ہیں: mockinggoose() ایک فنکشن انجام دیتا ہے اور ماڈل کو ماڈل کو منتقل کرتا ہے۔ پھر ہم toReturn ایک میچر جو وضاحت کرتا ہے کہ کیا واپس کیا جانا چاہئے؛ toReturn میچر کو دو دلائل کی توقع ہے۔

یہ دلائل ماڈل کے لیے ایک جعلی ڈیٹاسیٹ اور ڈیٹا کے ساتھ کام کرنے کے لیے استعمال کرنے کے لیے Mongo طریقہ ہیں۔ یہ مثال استعمال کرتی ہے: save کیونکہ ہم کاغذ پر ریکارڈ بنا رہے ہیں۔

API یونٹ کی جانچ

آپ ایڈ کنٹیکٹ API کال کے لیے اپنا پہلا ٹیسٹ لکھ کر شروع کر سکتے ہیں، اس طرح:

import { describe, expect, it, vi } from "vitest";
import mockinggoose from "mockingoose";
import Contacts from "../schema/contactList";
import addContacts from "../src/controllers/add-contacts.controller";
import { type Response, type Request } from "express";

vi.mock("../utils/email-validation", () => {
  return { default: vi.fn().mockReturnValue(true) };
});

import validateEmail from "../utils/email-validation";

const fakeContact = {
  contactName: "arnold",
  phoneNumber: 798600102,
  email: "arnoldjabo@gmail.com",
};

describe("Add contacts to the database", async () => {
  it("successfully create a new contact to the database", async () => {
    mockinggoose(Contacts).toReturn(fakeContact, "save");

    const req = {
      body: fakeContact,
    } as Request;

    const res = {
      status: vi.fn().mockReturnThis(),
      json: vi.fn(),
    } as any as Response;

    await addContacts(req, res);
    expect(res.status).toHaveBeenCalledWith(201);
    expect(res.json).toHaveBeenCalledWith({
      message: `successfully created ${fakeContact.contactName}`,
    });
  });
 });

یہ ٹیسٹ دعوی مختلف میچرز کا استعمال کرتا ہے: جاسوس. اس کا استعمال فنکشنز کے لیے چیزوں کو چیک کرنے کے لیے کیا جاتا ہے جیسے کہ اسے کتنی بار بلایا گیا تھا یا اسے کال کرنے کے لیے کون سے پیرامیٹرز استعمال کیے گئے تھے۔

یہاں ہم توقع کرتے ہیں کہ جواب کے اسٹیٹس فنکشن کو بطور دلیل 201 اسٹیٹس کے ساتھ بلایا جائے گا۔ اس کے بعد JSON آبجیکٹ کو جواب بھیجنے کے لیے استعمال ہونے والے پیغام کی دلیل کے ساتھ بلایا جاتا ہے۔

انضمام کی جانچ

انٹیگریشن ٹیسٹنگ کسی ایپلیکیشن کے مختلف حصوں کے مواصلات اور انضمام کی جانچ کرتی ہے۔ مثال کے طور پر، آپ اس بات کو یقینی بنا سکتے ہیں کہ آپ کا ڈیٹا بیس ان فنکشنز کے ساتھ اچھی طرح سے مربوط ہے جو ڈیٹا بیس پر API کال کرتے ہیں۔

یونٹ ٹیسٹ کے برعکس (جہاں آپ کو API کال کرنے کے لیے ٹیسٹ کی ضرورت نہیں ہوتی ہے)، انضمام ٹیسٹ یہ جانچتے ہیں کہ آیا آپ کی ایپلیکیشن کے کچھ حصے ایک دوسرے کے ساتھ مربوط ہیں اور توقع کے مطابق کام کرتے ہیں۔ کسی چیز کا مذاق اڑانے کی ضرورت نہیں ہے کیونکہ میں صرف اس بات کو یقینی بنانا چاہتا ہوں کہ یہ بیک اینڈ کو کامیابی کے ساتھ درخواست بھیجتا ہے اور ڈیٹا بیس سے جڑ جاتا ہے۔

انٹیگریشن ٹیسٹ ڈیٹا ریپوزٹری بنائیں

ٹیسٹ ماحول بنانے کے دو طریقے ہیں جو انٹیگریشن ٹیسٹ چلاتے وقت ڈیٹا بیس کے طور پر کام کرتا ہے۔ ان میں شامل ہیں:

  • اپنے اصلی ڈیٹا بیس کا ایک ریپلیکا سکیما بنائیں اور کاپی کو ٹیسٹ ڈیٹا بیس ماحول کے طور پر استعمال کریں۔ جب بھی آپ ٹیسٹ چلاتے ہیں، آپ ڈیٹا بیس کنکشن کو ٹیسٹ DB کی طرف اشارہ کرتے ہیں۔

  • ان میموری ڈیٹا بیس اسٹوریج بناتا ہے۔ یہ نقطہ نظر دو الگ الگ اسکیموں کی ضرورت کو ختم کرتا ہے، ایک جانچ کے لیے اور ایک پیداوار کے لیے۔ اس کے بجائے، اسی اسکیما فارم کو کوڈ بیس میموری میں کنفیگر کریں اور اسے ٹیسٹنگ ماحول کے طور پر استعمال کریں۔

پہلا نقطہ نظر استعمال کرنا پیچیدہ ہے کیونکہ اس کے لیے آپ کے ماحول کے لیے ڈیٹا بیس ترتیب دینے اور ترتیب دینے کی ضرورت ہوتی ہے۔ تاہم، دوسرے اپروچ کے ساتھ، آپ اصل اسکیما کے ساتھ صحیح اسکیما ڈھانچہ ترتیب دے سکتے ہیں اور اسے ڈیٹا بیس میں کنفیگر کیے بغیر اور اسے استعمال کے بعد ہٹا کر جانچ کے لیے استعمال کر سکتے ہیں۔

Mongo DB کے لیے، ایسے پیکجز ہیں جو میموری میں اسٹوریج کے اختیارات کو آسان بناتے ہیں۔ mongodb-memory-server. ٹیسٹ رن مکمل ہونے کے بعد، ٹیسٹ میں استعمال ہونے والا تمام ڈیٹا حذف کر دیں۔

انضمام کی جانچ کے لیے ماحول کیسے ترتیب دیا جائے۔

آپ کو انسٹال کرنے کی ضرورت ہوگی:

  • supertest: ایک پیکیج جو آپ کو API کالز/درخواستیں کرنے اور جانچ کے وقت جوابات واپس کرنے میں مدد کرتا ہے۔

  • mongodb-memory-server: ایک پیکج جو ٹیسٹ ڈیٹا کے لیے ان میموری ڈیٹا بیس اسٹوریج بناتا ہے۔

# command for pnpm package manager
pnpm add -D mongodb-memory-server supertest @types/supertest

# command for npm package manager
npm install --save-dev mongodb-memory-server supertest @types/supertest

# command for yarn package manager
yarn add --dev mongodb-memory-server supertest @types/supertest

# command for bun package manager
bun add -d mongodb-memory-server supertest @types/supertest

آگے بڑھنے سے پہلے، آپ کو سرور انٹری فائل میں ترتیبات کو تبدیل کرنے کی ضرورت ہوگی۔

اگر آپ نے نوڈ یا کسی دوسرے فریم ورک میں ایکسپریس کا استعمال کیا ہے، تو آپ سرور آئٹم فائل کے لیے درج ذیل قسم کی ترتیبات سے واقف ہوں گے، جہاں سب کچھ ایک فائل میں شامل کیا جاتا ہے۔

import express from "express";
import { loadEnvFile } from "node:process";
import connectToDB from "../config/dbConfig";
import addContacts from "./controllers/add-contacts.controller";

const app = express();
loadEnvFile();
async function dbConnection() {
  await connectToDB();
}
dbConnection();

app.use(express.json());
app.post("/add-contacts", addContacts);
app.listen(5000, () => console.log("the server successfully connected"));

یہ ترتیب ٹھیک کام کرتی ہے اور بعض صورتوں میں درست ہے۔ تاہم، انضمام کی جانچ کرتے وقت یہ مسئلہ ہو سکتا ہے۔ اس کی وجہ یہ ہے کہ ہمیں اپنے انٹیگریشن ٹیسٹوں میں درخواستیں کرتے وقت استعمال کرنے کے لیے ایکسپریس کی مثال درکار ہے۔ اگر ہم برآمد کرنا چاہتے ہیں۔ app اگر آپ جانچ کے دوران درخواست کرتے ہیں، تو آپ کا پروڈکشن DB کنکشن آپ کے ٹیسٹ DB کنکشن سے متصادم ہوگا۔ اس کی وجہ سے ٹیسٹ کام کرنا بند کر دیتا ہے۔

یہاں کام ایک اور فائل بنانا، اپنی ایکسپریس مثال کی وضاحت کرنا اور اسے برآمد کرنا ہے۔ پھر، سرور سے ایکسپورٹ کردہ ایکسپریس مثال کا استعمال کرتے ہوئے سرور شروع کریں۔ ترتیبات مندرجہ ذیل ہیں:

app.ts

import express from "express";
import addContacts from "./controllers/add-contacts.controller";

const app = express()

app.use(express.json())
app.post("/add-contacts", addContacts);

export default app;

پھر مین فائل server.ts یا main.ts استعمال کریں app متغیرات جیسے:

import { loadEnvFile } from "node:process";
import connectToDB from "../config/dbConfig";
import app from "./app";

loadEnvFile();

async function bootsrap() {
  await connectToDB();
  app.listen(5000, () => console.log("the server successfully connected"));
}
bootsrap();

ایکسپریس مثال سے نکالا جا رہا ہے: app فائل بنانے کے بعد bootstrap() ڈیٹا بیس قائم کرنے اور سرور شروع کرنے کی صلاحیت۔ یہ ہمیں انٹیگریشن ٹیسٹ لکھنا شروع کرنے کی اجازت دیتا ہے: addContact ماڈیول

انضمام ٹیسٹ کیسے لکھیں۔

ٹپ:انٹیگریشن ٹیسٹ آپ کو فائل کے نام اس طرح بتانے کی اجازت دیتے ہیں: filename.integration.test.ts یہ انضمام کے ٹیسٹ کے لیے سب سے زیادہ استعمال ہونے والا نام دینے کا کنونشن ہے، لیکن اس کی ضرورت نہیں ہے۔ یہ صرف ایک نام دینے کا کنونشن ہے۔

سب سے پہلے آپ کو استعمال کرتے ہوئے ڈیٹا بیس ٹیسٹ ڈیٹا اسٹور قائم کرنے کی ضرورت ہے: mongoose اور mongdb-memory-server درخواست کے لیے پیکیج اور ایکسپریس مثال۔

import { afterAll, beforeAll, describe, expect, it } from "vitest";

import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import app from "../src/app";


describe("intergration test setup for add contact api", () => {
  let mongoServer: MongoMemoryServer;
  let server: any;

  beforeAll(async () => {
    mongoServer = await MongoMemoryServer.create();
    const uri = mongoServer.getUri();
    await mongoose.connect(uri);
    server = app.listen(0);
  });

  afterAll(async () => {
    mongoServer.stop();
    mongoose.disconnect();
    server.close();
  });
});

کہ beforeAll اور afterAll فنکشن ہے vitest فنکشن beforeAll ٹیسٹ رن شروع ہونے سے پہلے چلتا ہے۔ afterAll تمام ٹیسٹ رنز مکمل ہونے کے بعد رنز۔

اپنے ٹیسٹوں میں، ہم ٹیسٹ چلانے سے پہلے ڈیٹا بیس اور ایکسپریس مثال قائم کرتے ہیں۔

سب سے پہلے، ہم mongoServer متغیر پھر اندر beforeAll بلاکس کا استعمال شروع کریں۔ MongoMemoryServer.create ڈیٹا سٹوریج کی جانچ کے لیے ایک ان میموری ڈیٹا بیس بنائیں۔ ہمیں کنکشن سٹرنگ کا استعمال کرتے ہوئے ملتا ہے: uri متغیرات کا استعمال کرتے ہوئے getUri طریقہ آخر میں، ہم منگوز کے استعمال سے تیار کردہ ان میموری کنکشن سٹرنگ سے جڑتے ہیں۔

سرور متغیر کو پورٹ 0 پر سننے والے ایکسپریس مثال کے لیے تفویض کیا گیا ہے، لیکن آپ کوئی بھی پورٹ نمبر استعمال کر سکتے ہیں جو آپ چاہتے ہیں۔ یہ صرف مظاہرے کے مقاصد کے لیے ہے۔ یہ آپ کے ٹیسٹ ماحول کے لیے ایک ایکسپریس مثال بنائے گا۔

میں afterAllتمام ٹیسٹ رنز مکمل ہونے کے بعد، سرور اور ان میموری DB کو بند کریں، پھر Mongoose مثال کو منقطع کریں۔

اسی کے اندر describe پھر ٹیسٹ کی تفصیل اور دعوے شامل کریں (جیسے آپ نے یونٹ ٹیسٹ کے لیے کیا تھا)۔

import { afterAll, beforeAll, describe, expect, it } from "vitest";
import { MongoMemoryServer } from "mongodb-memory-server";
import mongoose from "mongoose";
import app from "../src/app";
import request from "supertest";
import Contacts from "../schema/contactList";

describe("intergration test for add contact api", () => {
  /*
    Here we do server setup and database in memory setup that was discussed,
    in the previous snippets for setting up integration data storage testing environment 
   */
  const fakeContact = {
    contactName: "arnold",
    phoneNumber: 798600102,
    email: "arnoldjabo@gmail.com",
  };

  describe("POST /add-contacts", () => {
    it("creates a new record to the database", async () => {
      const response = await request(app)
        .post("/add-contacts")
        .send(fakeContact);

       const contactList = await Contacts.findOne({
        email: "arnoldjabo@gmail.com",
      })!;
      expect(contactList?.email).toBe("arnoldjabo@gmail.com");
      console.log(contactList);

      expect(response.status).toBe(201);
      expect(response.body).toEqual({
        message: `successfully created ${fakeContact.contactName}`,
      });
    });
  });
});

یہاں ٹیسٹ فائل ٹیسٹ کو ایڈ کنٹیکٹ اینڈ پوائنٹ کے پوسٹ کے طریقہ کار کے ٹیسٹ کے طور پر بیان کرتی ہے۔ پھر جانچیں کہ آیا ڈیٹا بیس میں ڈیٹا شامل کیا گیا ہے۔

کے اندر it جسم، جو ہم استعمال کرتے ہیں۔ request سے supertest ہم اپنے بنائے ہوئے سرور سے درخواست کرتے ہیں۔ آپ اختتامی نقطہ اور HTTP طریقہ کو بھی منسلک کرتے ہیں جس کی آپ جانچ کرنا چاہتے ہیں۔

ان طریقوں کے لیے جو بیک اینڈ پر ڈیٹا بھیجتے ہیں، جیسے POST، PATCH، یا PUT، استعمال کریں: send() طریقہ request ہم جو ڈیٹا بھیج رہے ہیں اس کے لیے ہم ایک آبجیکٹ شامل کرتے ہیں۔

ہم جواب کا استعمال اس بات پر بحث کرنے کے لیے کریں گے کہ جواب کیسا ہو سکتا ہے۔ مثال کے طور پر، سرور کامیاب ڈیٹا انٹری پر 201 کا اسٹیٹس کوڈ اور ایک JSON آبجیکٹ فراہم کرنے کی توقع رکھتا ہے جس میں اس بات کی تصدیق ہوتی ہے کہ رابطہ شامل کیا گیا ہے۔

ہم یہ جانچنے کے لیے دعوے استعمال کر رہے ہیں کہ جواب کی حالت ہماری توقع سے میل کھاتی ہے، اور یہ کہ جوابی جسم اس متوقع پیغام سے میل کھاتا ہے جو ہمیں موصول ہونا چاہیے۔

یہ جانچنے کے لیے کہ ڈیٹا کو درحقیقت ڈیٹا بیس میں شامل کیا جا رہا ہے، میں نے درج ذیل کو شامل کیا: contactList ان رابطوں کو تلاش کرنے اور درآمد کرنے کے لیے جنہیں ہم نے ابھی ای میل کے ذریعے شامل کیا ہے، پھر ہم contactList کنسول دکھاتا ہے کہ ان میموری کیسے کام کرتی ہے۔ یہ تقریباً ایک حقیقی Mongo DB مثال سے مماثل ہے۔ جب میں ٹیسٹ چلاتا ہوں تو مجھے درج ذیل نتائج ملتے ہیں:

کنسول لاگز دکھاتے ہیں کہ میموری ڈیٹا بیس کا استعمال کرتے ہوئے ذخیرہ کیا گیا مواد اسکرین پر پرنٹ ہونے پر کیسا لگتا ہے، اور انضمام کی جانچ کے لیے آؤٹ پٹ۔

آپ کنسول میں دیکھ سکتے ہیں کہ ڈیٹا کو ایک باقاعدہ مونگو ڈیٹا بیس کی طرح میموری میں محفوظ اور بازیافت کیا جاتا ہے۔

یونٹ اور انضمام ٹیسٹ کب استعمال کریں۔

تو آپ ہر قسم کے ٹیسٹ کب استعمال کرتے ہیں؟

اگر آپ کے پاس خالص فعالیت ہے، جیسے پہلے کی ای میل کی توثیق کی مثال، یونٹ ٹیسٹ استعمال کریں۔

اور ان فنکشنز کے لیے انٹیگریشن ٹیسٹ کا استعمال کریں جو بیرونی API کالز کرتے ہیں جو کہ بیرونی سروسز پر منحصر ہوتے ہیں، جیسے ڈیٹا بیس کالز، آپ کے درآمد کردہ ہر فنکشن کا مذاق اڑانے سے بچنے کے لیے۔

خلاصہ

یہ گائیڈ بتاتا ہے کہ دو قسم کے ٹیسٹ کیسے کام کرتے ہیں: یونٹ ٹیسٹ اور انٹیگریشن ٹیسٹ۔

یونٹ ٹیسٹ وہ کوڈ ہیں جو انفرادی طور پر آپ کے کوڈ بیس کے مخصوص حصوں کی جانچ کرتے ہیں اور خالص فعالیت کے لیے بہترین موزوں ہوتے ہیں۔ انٹیگریشن ٹیسٹ وہ کوڈ ہیں جو کسی ایپلیکیشن کے حصوں کے درمیان کامیاب انضمام اور مواصلات کی جانچ کرتے ہیں اور غیر خالص فعالیت کے لیے بہترین موزوں ہیں۔

اگر آپ کو مضمون کارآمد لگا تو براہ کرم مجھے ایک کافی خریدیں۔

Scroll to Top