ضمیمه و: نمونه‌های اضافی برای تست (فصل ۹)

این ضمیمه جزئیات کامل پیاده‌سازی تست‌های اضافی برای اپلیکیشن مرکز اجاره کشتی‌های کروز را ارائه می‌دهد، با تمرکز بر تست‌های کامپوننت‌ها، مسیرهای سرور، و storeها. این بخش جنبه‌هایی که در فصل نهم یا ضمیمه‌های اولیه به‌صورت خلاصه یا غایب بودند را به‌طور جامع پوشش می‌دهد، تا اطمینان حاصل شود که اپلیکیشن به‌طور کامل تست شده و از پایداری و کیفیت بالایی برخوردار است.


نصب و پیکربندی ابزار تست

برای انجام تست‌ها، از Vitest به‌عنوان فریم‌ورک تست و @nuxt/test-utils برای ادغام با Nuxt 3 استفاده می‌کنیم. این ابزارها امکان تست واحد، تست یکپارچگی، و تست‌های End-to-End را فراهم می‌کنند.

نصب وابستگی‌ها

  1. نصب Vitest و ابزارهای مربوطه:
   npm install --save-dev vitest @vue/test-utils @nuxt/test-utils
  1. به‌روزرسانی package.json:
   {
     "scripts": {
       "test": "vitest",
       "test:coverage": "vitest --coverage"
     }
   }
  1. ایجاد فایل پیکربندی Vitest (vitest.config.ts):
   import { defineConfig } from 'vitest/config';
   import vue from '@vitejs/plugin-vue';

   export default defineConfig({
     plugins: [vue()],
     test: {
       environment: 'jsdom', // برای شبیه‌سازی DOM در تست‌های Vue
       globals: true, // فعال‌سازی توابع جهانی مانند describe, it, expect
       setupFiles: ['./tests/setup.ts'], // فایل تنظیمات اولیه
       coverage: {
         provider: 'v8',
         reporter: ['text', 'html'],
         include: ['components/**/*.{vue,ts}', 'stores/**/*.{ts}', 'server/api/**/*.{ts}'],
       },
     },
   });
  1. فایل تنظیمات اولیه (tests/setup.ts):
   import { config } from '@vue/test-utils';
   import { createPinia } from 'pinia';
   import { defineNuxtConfig } from 'nuxt/config';

   config.global.plugins = [createPinia()];
  • توضیحات:

  • environment: 'jsdom': برای شبیه‌سازی محیط مرورگر در تست‌های Vue.

  • globals: true: نیاز به وارد کردن دستی describe, it, و expect را حذف می‌کند.
  • coverage: گزارش پوشش کد برای فایل‌های Vue و TypeScript در پوشه‌های components, stores, و server/api.

تست‌های اضافی

در ادامه، تست‌های اضافی برای کامپوننت‌ها، مسیرهای سرور، و storeها ارائه شده است. این تست‌ها مکمل تست‌های اولیه ارائه‌شده برای ShipCard.vue و /api/ships هستند.

۱. تست برای کامپوننت ShipCard.vue

تست‌های زیر عملکرد و رندر صحیح کامپوننت ShipCard.vue را بررسی می‌کنند.

// tests/components/ShipCard.test.ts
import { mount } from '@vue/test-utils';
import ShipCard from '~/components/ShipCard.vue';
import { describe, it, expect } from 'vitest';

describe('ShipCard', () => {
  const ship = {
    id: 1,
    name: 'Ocean Explorer',
    capacity: 500,
    pricePerDay: 10000,
    amenities: ['Pool', 'Gym'],
    image: '/images/ocean.jpg',
  };

  it('renders ship name and price', () => {
    const wrapper = mount(ShipCard, { props: { ship } });
    expect(wrapper.find('h2').text()).toBe('Ocean Explorer');
    expect(wrapper.text()).toContain('$10000/day');
  });

  it('renders ship image with correct src', () => {
    const wrapper = mount(ShipCard, { props: { ship } });
    const img = wrapper.find('img');
    expect(img.attributes('src')).toContain('/images/ocean.jpg');
  });

  it('renders link to ship details with correct href', () => {
    const wrapper = mount(ShipCard, { props: { ship } });
    const link = wrapper.find('a');
    expect(link.attributes('href')).toBe('/ships/1');
  });

  it('emits click event when link is clicked', async () => {
    const wrapper = mount(ShipCard, { props: { ship } });
    await wrapper.find('a').trigger('click');
    expect(wrapper.emitted('click')).toBeTruthy();
  });
});
  • توضیحات:

  • تست اول: بررسی می‌کند که نام و قیمت کشتی به‌درستی رندر شوند.

  • تست دوم: اطمینان می‌دهد که تصویر کشتی با مسیر صحیح نمایش داده می‌شود.
  • تست سوم: بررسی می‌کند که لینک "View Details" به مسیر درست (/ships/1) اشاره کند.
  • تست چهارم: بررسی می‌کند که کلیک روی لینک رویداد click را منتشر کند (در صورت استفاده از emit).

۲. تست برای کامپوننت BookingForm.vue

تست‌های زیر عملکرد فرم رزرو و اعتبارسنجی آن را بررسی می‌کنند.

// tests/components/BookingForm.test.ts
import { mount } from '@vue/test-utils';
import BookingForm from '~/components/BookingForm.vue';
import { describe, it, expect } from 'vitest';

describe('BookingForm', () => {
  const ship = {
    id: 1,
    name: 'Ocean Explorer',
    capacity: 500,
    pricePerDay: 10000,
    amenities: [],
    image: '/images/ocean.jpg',
  };

  it('renders form inputs', () => {
    const wrapper = mount(BookingForm, { props: { ship } });
    expect(wrapper.findAll('input[type="date"]').length).toBe(2);
    expect(wrapper.find('button[type="submit"]').text()).toBe('Book Now');
  });

  it('displays validation errors for empty fields', async () => {
    const wrapper = mount(BookingForm, { props: { ship } });
    await wrapper.find('form').trigger('submit');
    expect(wrapper.findAll('.error').length).toBe(2);
    expect(wrapper.find('.error').text()).toBe('Start date is required');
  });

  it('emits submit event with valid data', async () => {
    const wrapper = mount(BookingForm, { props: { ship } });
    await wrapper.find('input[type="date"]').setValue('2025-10-20');
    await wrapper.findAll('input[type="date"]')[1].setValue('2025-10-22');
    await wrapper.find('form').trigger('submit');
    expect(wrapper.emitted('submit')).toBeTruthy();
    expect(wrapper.emitted('submit')[0][0]).toEqual({
      startDate: '2025-10-20',
      endDate: '2025-10-22',
    });
  });
});
  • توضیحات:

  • تست اول: بررسی می‌کند که فیلدهای ورودی و دکمه ارسال فرم به‌درستی رندر شوند.

  • تست دوم: اطمینان می‌دهد که خطاهای اعتبارسنجی برای فیلدهای خالی نمایش داده شوند.
  • تست سوم: بررسی می‌کند که فرم با داده‌های معتبر رویداد submit را با مقادیر صحیح منتشر کند.

۳. تست برای مسیر /api/ships

تست‌های زیر عملکرد مسیر سرور /api/ships را بررسی می‌کنند.

// tests/server/api/ships.test.ts
import { describe, it, expect } from 'vitest';
import { setup, $fetch } from '@nuxt/test-utils';

describe('Ships API', () => {
  it('returns list of ships', async () => {
    await setup();
    const ships = await $fetch('/api/ships');
    expect(ships).toBeInstanceOf(Array);
    expect(ships[0]).toHaveProperty('name', 'Ocean Explorer');
    expect(ships[0]).toHaveProperty('pricePerDay', 10000);
  });

  it('returns correct ship data structure', async () => {
    await setup();
    const ships = await $fetch('/api/ships');
    expect(ships[0]).toMatchObject({
      id: expect.any(Number),
      name: expect.any(String),
      capacity: expect.any(Number),
      pricePerDay: expect.any(Number),
      amenities: expect.any(Array),
      image: expect.any(String),
    });
  });
});
  • توضیحات:

  • تست اول: بررسی می‌کند که API یک آرایه از کشتی‌ها با داده‌های صحیح (مانند name) برمی‌گرداند.

  • تست دوم: اطمینان می‌دهد که ساختار داده‌های بازگشتی با مدل Ship مطابقت دارد.

۴. تست برای مسیر /api/bookings

تست‌های زیر عملکرد مسیر سرور /api/bookings را بررسی می‌کنند.

// tests/server/api/bookings.test.ts
import { describe, it, expect } from 'vitest';
import { setup, $fetch } from '@nuxt/test-utils';

describe('Bookings API', () => {
  it('returns empty array initially', async () => {
    await setup();
    const bookings = await $fetch('/api/bookings');
    expect(bookings).toEqual([]);
  });

  it('creates a new booking', async () => {
    await setup();
    const newBooking = {
      shipId: 1,
      startDate: '2025-10-20',
      endDate: '2025-10-22',
      totalPrice: 20000,
    };
    const createdBooking = await $fetch('/api/bookings', {
      method: 'POST',
      body: newBooking,
    });
    expect(createdBooking).toMatchObject({
      id: expect.any(Number),
      shipId: 1,
      totalPrice: 20000,
    });
    const bookings = await $fetch('/api/bookings');
    expect(bookings).toContainEqual(createdBooking);
  });
});
  • توضیحات:

  • تست اول: بررسی می‌کند که API در ابتدا آرایه خالی برمی‌گرداند.

  • تست دوم: اطمینان می‌دهد که رزرو جدید با موفقیت ایجاد شده و در لیست رزروها ظاهر می‌شود.

۵. تست برای Store (stores/ships.ts)

تست‌های زیر عملکرد store مربوط به کشتی‌ها را بررسی می‌کنند.

// tests/stores/ships.test.ts
import { setActivePinia, createPinia } from 'pinia';
import { useShipsStore } from '~/stores/ships';
import { describe, it, expect, beforeEach } from 'vitest';
import { $fetch } from '@nuxt/test-utils';

vi.mock('@nuxt/test-utils', () => ({
  $fetch: vi.fn(),
}));

describe('Ships Store', () => {
  beforeEach(() => {
    setActivePinia(createPinia());
  });

  it('fetches ships from API', async () => {
    const mockShips = [
      { id: 1, name: 'Ocean Explorer', capacity: 500, pricePerDay: 10000, amenities: [], image: '/images/ocean.jpg' },
    ];
    $fetch.mockResolvedValue(mockShips);
    const store = useShipsStore();
    await store.fetchShips();
    expect(store.ships).toEqual(mockShips);
  });

  it('filters ships by search query', () => {
    const store = useShipsStore();
    store.ships = [
      { id: 1, name: 'Ocean Explorer', capacity: 500, pricePerDay: 10000, amenities: [], image: '/images/ocean.jpg' },
      { id: 2, name: 'Luxury Liner', capacity: 1000, pricePerDay: 20000, amenities: [], image: '/images/luxury.jpg' },
    ];
    store.setSearch('Ocean');
    expect(store.filteredShips).toHaveLength(1);
    expect(store.filteredShips[0].name).toBe('Ocean Explorer');
  });
});
  • توضیحات:

  • تست اول: بررسی می‌کند که متد fetchShips داده‌ها را از API دریافت کرده و در store ذخیره می‌کند.

  • تست دوم: اطمینان می‌دهد که filteredShips کشتی‌ها را بر اساس پرس‌وجوی جستجو فیلتر می‌کند.

چرا این بخش در ضمیمه‌ها خلاصه بود؟

ضمیمه‌های اولیه فقط یک تست نمونه برای store ارائه کردند و تست‌های جامع برای کامپوننت‌ها (ShipCard.vue, BookingForm.vue) و مسیرهای سرور (/api/ships, /api/bookings) را پوشش ندادند. این ضمیمه با ارائه تست‌های اضافی، پوشش کامل‌تری برای اطمینان از کیفیت کد فراهم می‌کند.


نکات عملی برای پیاده‌سازی

  1. راه‌اندازی محیط تست:

  2. فایل vitest.config.ts و tests/setup.ts را در پروژه ایجاد کنید.

  3. دستور npm run test را برای اجرای تست‌ها و npm run test:coverage را برای گزارش پوشش کد اجرا کنید.

  4. تست در محیط توسعه:

  5. با npm run dev، اپلیکیشن را اجرا کنید و اطمینان حاصل کنید که APIها و کامپوننت‌ها به‌درستی کار می‌کنند.

  6. از DevTools مرورگر برای دیباگ استفاده کنید.

  7. افزودن تست‌های بیشتر:

  8. برای AppHeader.vue:

     it('displays user name when authenticated', async () => {
       const wrapper = mount(AppHeader, {
         global: { plugins: [createPinia()] },
         mocks: { $auth: { data: { user: { name: 'John Doe' } } } },
       });
       expect(wrapper.text()).toContain('John Doe');
     });
  • برای /api/stripe-checkout:
     it('creates a Stripe checkout session', async () => {
       await setup();
       const response = await $fetch('/api/stripe-checkout', {
         method: 'POST',
         body: { booking: { shipId: 1, totalPrice: 20000 } },
       });
       expect(response).toHaveProperty('id');
     });
  1. پوشش کد:

  2. هدف پوشش حداقل ۸۰٪ برای فایل‌های components, stores, و server/api باشد.

  3. گزارش‌های پوشش را در پوشه coverage بررسی کنید.

جمع‌بندی ضمیمه و

این ضمیمه تست‌های اضافی برای کامپوننت‌ها (ShipCard.vue, BookingForm.vue), مسیرهای سرور (/api/ships, /api/bookings), و store (ships.ts) را ارائه داد، همراه با پیکربندی Vitest و نکات عملی. این تست‌ها کیفیت و پایداری اپلیکیشن را تضمین می‌کنند. برای اطلاعات بیشتر، به فصل نهم یا مخزن فرضی GitHub (https://github.com/khosronz/cruise-rental-app) مراجعه کنید.