Skip to content

اختبار اللقطات يحفظ مخرجات قيمة ويقارنها بتشغيلات الاختبار المستقبلية. هذا مفيد بشكل خاص لمكونات واجهة المستخدم والكائنات المعقدة أو أي مخرجات تحتاج إلى البقاء متسقة.

اللقطات الأساسية

تُكتب اختبارات اللقطات باستخدام أداة المطابقة .toMatchSnapshot():

test.ts
ts
import { test, expect } from "bun:test";

test("snap", () => {
  expect("foo").toMatchSnapshot();
});

في المرة الأولى التي يتم فيها تشغيل هذا الاختبار، سيتم تسلسل الوسيطة لـ expect وكتابتها إلى ملف لقطة خاص في دليل __snapshots__ بجانب ملف الاختبار.

ملفات اللقطات

بعد تشغيل الاختبار أعلاه، سيقوم Bun بإنشاء:

هيكل الدليل
text
your-project/
├── snap.test.ts
└── __snapshots__/
    └── snap.test.ts.snap

يحتوي ملف اللقطة على:

__snapshots__/snap.test.ts.snap
ts
// Bun Snapshot v1, https://bun.com/docs/test/snapshots

exports[`snap 1`] = `"foo"`;

في التشغيلات المستقبلية، تتم مقارنة الوسيطة مع اللقطة على القرص.

تحديث اللقطات

يمكن إعادة إنشاء اللقطات بالأمر التالي:

bash
bun test --update-snapshots

هذا مفيد عندما:

  • قمت بتغيير المخرجات عمدًا
  • تضيف اختبارات لقطات جديدة
  • تغيرت المخرجات المتوقعة بشكل شرعي

اللقطات المضمنة

للقيم الأصغر، يمكنك استخدام اللقطات المضمنة مع .toMatchInlineSnapshot(). يتم تخزين هذه اللقطات مباشرة في ملف الاختبار الخاص بك:

test.ts
ts
import { test, expect } from "bun:test";

test("inline snapshot", () => {
  // التشغيل الأول: سيتم إدراج اللقطة تلقائيًا
  expect({ hello: "world" }).toMatchInlineSnapshot();
});

بعد التشغيل الأول، يقوم Bun تلقائيًا بتحديث ملف الاختبار:

test.ts
ts
import { test, expect } from "bun:test";

test("inline snapshot", () => {
  expect({ hello: "world" }).toMatchInlineSnapshot(`
{
  "hello": "world",
}
`);
});

استخدام اللقطات المضمنة

  1. اكتب اختبارك مع .toMatchInlineSnapshot()
  2. شغّل الاختبار مرة واحدة
  3. يقوم Bun تلقائيًا بتحديث ملف الاختبار باللقطة
  4. في التشغيلات اللاحقة، سيتم مقارنة القيمة مع اللقطة المضمنة

اللقطات المضمنة مفيدة بشكل特别 للقيم الصغيرة والبسيطة حيث يكون من المفيد رؤية المخرجات المتوقعة مباشرة في ملف الاختبار.

لقطات الأخطاء

يمكنك أيضًا التقاط رسائل الأخطاء باستخدام .toThrowErrorMatchingSnapshot() و .toThrowErrorMatchingInlineSnapshot():

test.ts
ts
import { test, expect } from "bun:test";

test("error snapshot", () => {
  expect(() => {
    throw new Error("Something went wrong");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Another error");
  }).toThrowErrorMatchingInlineSnapshot();
});

بعد التشغيل، يصبح الإصدار المضمن:

test.ts
ts
test("error snapshot", () => {
  expect(() => {
    throw new Error("Something went wrong");
  }).toThrowErrorMatchingSnapshot();

  expect(() => {
    throw new Error("Another error");
  }).toThrowErrorMatchingInlineSnapshot(`"Another error"`);
});

استخدام اللقطات المتقدم

الكائنات المعقدة

تعمل اللقطات بشكل جيد مع الكائنات المتداخلة المعقدة:

test.ts
ts
import { test, expect } from "bun:test";

test("complex object snapshot", () => {
  const user = {
    id: 1,
    name: "John Doe",
    email: "john@example.com",
    profile: {
      age: 30,
      preferences: {
        theme: "dark",
        notifications: true,
      },
    },
    tags: ["developer", "javascript", "bun"],
  };

  expect(user).toMatchSnapshot();
});

لقطات المصفوفات

المصفوفات أيضًا مناسبة جيدًا لاختبار اللقطات:

test.ts
ts
import { test, expect } from "bun:test";

test("array snapshot", () => {
  const numbers = [1, 2, 3, 4, 5].map(n => n * 2);
  expect(numbers).toMatchSnapshot();
});

لقطات مخرجات الدوال

التقط لقطات لمخرجات الدوال:

test.ts
ts
import { test, expect } from "bun:test";

function generateReport(data: any[]) {
  return {
    total: data.length,
    summary: data.map(item => ({ id: item.id, name: item.name })),
    timestamp: "2024-01-01", // ثابت للاختبار
  };
}

test("report generation", () => {
  const data = [
    { id: 1, name: "Alice", age: 30 },
    { id: 2, name: "Bob", age: 25 },
  ];

  expect(generateReport(data)).toMatchSnapshot();
});

لقطات مكونات React

اللقطات مفيدة بشكل خاص لمكونات React:

test.ts
tsx
import { test, expect } from "bun:test";
import { render } from "@testing-library/react";

function Button({ children, variant = "primary" }) {
  return <button className={`btn btn-${variant}`}>{children}</button>;
}

test("Button component snapshots", () => {
  const { container: primary } = render(<Button>Click me</Button>);
  const { container: secondary } = render(<Button variant="secondary">Cancel</Button>);

  expect(primary.innerHTML).toMatchSnapshot();
  expect(secondary.innerHTML).toMatchSnapshot();
});

أدوات مطابقة الخصائص

للقيم التي تتغير بين تشغيلات الاختبار (مثل الطوابع الزمنية أو المعرفات)، استخدم أدوات مطابقة الخصائص:

test.ts
ts
import { test, expect } from "bun:test";

test("snapshot with dynamic values", () => {
  const user = {
    id: Math.random(), // هذا يتغير في كل تشغيل
    name: "John",
    createdAt: new Date().toISOString(), // هذا أيضًا يتغير
  };

  expect(user).toMatchSnapshot({
    id: expect.any(Number),
    createdAt: expect.any(String),
  });
});

سيخزن ملف اللقطة:

ملف اللقطة
txt
exports[`snapshot with dynamic values 1`] = `
{
  "createdAt": Any<String>,
  "id": Any<Number>,
  "name": "John",
}
`;

أدوات التسلسل المخصصة

يمكنك تخصيص كيفية تسلسل الكائنات في اللقطات:

test.ts
ts
import { test, expect } from "bun:test";

// أداة تسلسل مخصصة لكائنات Date
expect.addSnapshotSerializer({
  test: val => val instanceof Date,
  serialize: val => `"${val.toISOString()}"`,
});

test("custom serializer", () => {
  const event = {
    name: "Meeting",
    date: new Date("2024-01-01T10:00:00Z"),
  };

  expect(event).toMatchSnapshot();
});

أفضل الممارسات

الحفاظ على اللقطات صغيرة

test.ts
ts
// جيد: لقطات مركزة
test("user name formatting", () => {
  const formatted = formatUserName("john", "doe");
  expect(formatted).toMatchInlineSnapshot(`"John Doe"`);
});

// تجنب: لقطات ضخمة يصعب مراجعتها
test("entire page render", () => {
  const page = renderEntirePage();
  expect(page).toMatchSnapshot(); // قد يكون هذا آلاف الأسطر
});

استخدام أسماء اختبار وصفية

test.ts
ts
// جيد: واضح ما تمثله اللقطة
test("formats currency with USD symbol", () => {
  expect(formatCurrency(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

// تجنب: غير واضح ما يتم اختباره
test("format test", () => {
  expect(format(99.99)).toMatchInlineSnapshot(`"$99.99"`);
});

تجميع اللقطات ذات الصلة

test.ts
ts
import { describe, test, expect } from "bun:test";

describe("Button component", () => {
  test("primary variant", () => {
    expect(render(<Button variant="primary">Click</Button>))
      .toMatchSnapshot();
  });

  test("secondary variant", () => {
    expect(render(<Button variant="secondary">Cancel</Button>))
      .toMatchSnapshot();
  });

  test("disabled state", () => {
    expect(render(<Button disabled>Disabled</Button>))
      .toMatchSnapshot();
  });
});

التعامل مع البيانات الديناميكية

test.ts
ts
// جيد: تطبيع البيانات الديناميكية
test("API response format", () => {
  const response = {
    data: { id: 1, name: "Test" },
    timestamp: Date.now(),
    requestId: generateId(),
  };

  expect({
    ...response,
    timestamp: "TIMESTAMP",
    requestId: "REQUEST_ID",
  }).toMatchSnapshot();
});

// أو استخدام أدوات مطابقة الخصائص
test("API response with matchers", () => {
  const response = getApiResponse();

  expect(response).toMatchSnapshot({
    timestamp: expect.any(Number),
    requestId: expect.any(String),
  });
});

إدارة اللقطات

مراجعة تغييرات اللقطات

عندما تتغير اللقطات، راجعها بعناية:

bash
# انظر ما تغير
git diff __snapshots__/

# تحديث إذا كانت التغييرات مقصودة
bun test --update-snapshots

# إيداع اللقطات المحدثة
git add __snapshots__/
git commit -m "Update snapshots after UI changes"

تنظيف اللقطات غير المستخدمة

سيحذر Bun من اللقطات غير المستخدمة:

تحذير
txt
Warning: 1 unused snapshot found:
  my-test.test.ts.snap: "old test that no longer exists 1"

قم بإزالة اللقطات غير المستخدمة بحذفها من ملفات اللقطات أو بتشغيل الاختبارات مع أعلام التنظيف إذا كانت متاحة.

تنظيم ملفات اللقطات الكبيرة

للمشاريع الكبيرة، فكر في تنظيم الاختبارات للحفاظ على ملفات اللقطات قابلة للإدارة:

هيكل الدليل
text
tests/
├── components/
│   ├── Button.test.tsx
│   └── __snapshots__/
│       └── Button.test.tsx.snap
├── utils/
│   ├── formatters.test.ts
│   └── __snapshots__/
│       └── formatters.test.ts.snap

استكشاف الأخطاء

فشل اللقطات

عندما تفشل اللقطات، سترى فرقًا:

diff
diff
- Expected
+ Received

  Object {
-   "name": "John",
+   "name": "Jane",
  }

الأسباب الشائعة:

  • تغييرات مقصودة (تحديث مع --update-snapshots)
  • تغييرات غير مقصودة (إصلاح الكود)
  • بيانات ديناميكية (استخدم أدوات مطابقة الخصائص)
  • اختلافات البيئة (طبيع البيانات)

اختلافات المنصة

كن على علم باختلافات المنصة المحددة:

test.ts
ts
// قد تختلف المسارات بين Windows/Unix
test("file operations", () => {
  const result = processFile("./test.txt");

  expect({
    ...result,
    path: result.path.replace(/\\/g, "/"), // تطبيع المسارات
  }).toMatchSnapshot();
});

Bun بواسطة www.bunjs.com.cn تحرير