"إذا أراد العامل أن يؤدي عمله بشكل جيد، فعليه أولاً أن يشحذ أدواته." - كونفوشيوس، "مختارات كونفوشيوس. لو لينجونج"
الصفحة الأمامية > برمجة > تجاوز الوظائف في الاختبارات الفردية باستخدام Jest

تجاوز الوظائف في الاختبارات الفردية باستخدام Jest

تم النشر بتاريخ 2024-11-02
تصفح:928

Override functions in individual tests using Jest

في بعض الأحيان تريد الاستهزاء بوظيفة ما في بعض الاختبارات دون غيرها. في بعض الأحيان تريد تقديم نماذج مختلفة لاختبارات مختلفة. يجعل Jest هذا أمرًا صعبًا: سلوكه الافتراضي هو تجاوز وظيفة الحزمة لملف اختبار كامل، وليس مجرد اختبار واحد. يبدو هذا غريبًا إذا كنت قد استخدمت أدوات مرنة مثل Python's @patch أو حاوية خدمة Laravel.

سيوضح لك هذا المنشور كيفية محاكاة وظائف الاختبارات الفردية، ثم الرجوع إلى التنفيذ الأصلي إذا لم يتم تقديم أي محاكاة. سيتم تقديم أمثلة لكل من وحدات CommonJS وES. ستعمل التقنيات الموضحة في هذا المنشور مع كل من وحدات الطرف الأول وحزم الطرف الثالث.

وحدات CommonJS مقابل ES

نظرًا لأننا سنغطي أنظمة وحدات متعددة في هذا المنشور، فمن المهم فهم ماهيتها.

CommonJS (يختصر CJS) هو نظام الوحدة في Node.js. يقوم بتصدير الوظائف باستخدام وظائف Module.exports واستيرادها باستخدام require():

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = { greet };
// CommonJS import

const getUsersList = require('./greet');

وحدات ES (اختصار ESM) هي نظام الوحدات الذي يستخدمه المتصفح. يقوم بتصدير الوظائف باستخدام الكلمة الأساسية للتصدير واستيراد الوظائف باستخدام الكلمة الأساسية للاستيراد:

// ES module export

export default function greet() {
  return "Hello, world!";
}
// ES module import

import { greet } from "./greet";

يستخدم معظم مطوري JavaScript للواجهة الأمامية وحدات ES في وقت كتابة هذا المنشور، ويستخدمها العديد من مطوري JS من جانب الخادم أيضًا. ومع ذلك، لا يزال CommonJS هو الإعداد الافتراضي لـ Node. بغض النظر عن النظام الذي تستخدمه، فمن المفيد قراءة المقالة بأكملها للتعرف على نظام السخرية الخاص بـ Jest.

السخرية من دالة مصدرة واحدة باستخدام CommonJS

عادةً ما يقوم ملف CommonJS بتصدير وحداته باستخدام بناء جملة الكائن، كما هو موضح أدناه:

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = { greet: greet };

ومع ذلك، من الممكن أيضًا تصدير دالة بنفسها:

// CommonJS export 

function greet() {
  return "Hello, world!";
}

module.exports = greet;

لا أوصي بالضرورة بالقيام بذلك في التعليمات البرمجية الخاصة بك: تصدير كائن سيمنحك مشاكل أقل أثناء تطوير تطبيقك. ومع ذلك، فمن الشائع أنه من المفيد مناقشة كيفية الاستهزاء بوظيفة مُصدَّرة مجردة في CommonJS، ثم الرجوع إلى الوظيفة الأصلية إذا لم يوفر الاختبار التنفيذ الخاص به.

لنفترض أن لدينا ملف CommonJS التالي الذي نرغب في الاستهزاء به أثناء الاختبارات:

// cjsFunction.js

function testFunc() {
  return "original";
}

module.exports = testFunc;

يمكننا الاستهزاء به في اختباراتنا باستخدام الكود التالي:

const testFunc = require("./cjsFunction");

jest.mock("./cjsFunction");

beforeEach(() => {
  testFunc.mockImplementation(jest.requireActual("./cjsFunction"));
});

it("can override the implementation for a single test", () => {
  testFunc.mockImplementation(() => "mock implementation");

  expect(testFunc()).toBe("mock implementation");
  expect(testFunc.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  testFunc.mockReturnValue("mock return value");

  expect(testFunc()).toBe("mock return value");
  expect(testFunc.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(testFunc()).toBe("original");
  expect(testFunc.mock.calls).toHaveLength(1);
});

كيف يعمل

عندما نستدعي jest.mock("./cjsFunction")، فإن هذا يستبدل الوحدة (الملف وجميع صادراته) بنموذج تلقائي (docs). عندما يتم استدعاء نموذج تلقائي، فإنه سيعود غير محدد. ومع ذلك، فإنه سيوفر طرقًا لتجاوز تنفيذ النموذج الوهمي، وقيمة الإرجاع، والمزيد. يمكنك رؤية جميع الخصائص والأساليب التي يوفرها في وثائق Jest Mock Functions.

يمكننا استخدام طريقة mockImplementation() الخاصة بالنموذج الوهمي لتعيين تطبيق النموذج تلقائيًا على تنفيذ الوحدة الأصلية. يوفر Jest التابع jest.requireActual()‎ الذي سيقوم دائمًا بتحميل الوحدة الأصلية، حتى لو تم الاستهزاء بها حاليًا.

يتم مسح التطبيقات الوهمية وقيم الإرجاع تلقائيًا بعد كل اختبار، حتى نتمكن من تمرير وظيفة رد الاتصال إلى وظيفة Jest beforeEach () التي تحدد تنفيذ النموذج الوهمي على التنفيذ الأصلي قبل كل اختبار. ثم يمكن لأي اختبارات ترغب في توفير قيمة الإرجاع أو التنفيذ الخاصة بها القيام بذلك يدويًا داخل نص الاختبار.

السخرية من CommonJS عند تصدير كائن

لنفترض أن الكود أعلاه قام بتصدير كائن بدلاً من وظيفة واحدة:

// cjsModule.js

function testFunc() {
  return "original";
}

module.exports = {
  testFunc: testFunc,
};

ستبدو اختباراتنا بعد ذلك كما يلي:

const cjsModule = require("./cjsModule");

afterEach(() => {
  jest.restoreAllMocks();
});

it("can override the implementation for a single test", () => {
  jest
    .spyOn(cjsModule, "testFunc")
    .mockImplementation(() => "mock implementation");

  expect(cjsModule.testFunc()).toBe("mock implementation");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  jest.spyOn(cjsModule, "testFunc").mockReturnValue("mock return value");

  expect(cjsModule.testFunc()).toBe("mock return value");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(cjsModule.testFunc()).toBe("original");
});

it("can spy on calls while keeping the original implementation", () => {
  jest.spyOn(cjsModule, "testFunc");

  expect(cjsModule.testFunc()).toBe("original");
  expect(cjsModule.testFunc.mock.calls).toHaveLength(1);
});

كيف يعمل

تسمح طريقة jest.spyOn() لـ Jest بتسجيل المكالمات إلى طريقة ما على كائن ما وتوفير بديل خاص بها. يعمل هذا فقط على الكائنات، ويمكننا استخدامه لأن وحدتنا تقوم بتصدير كائن يحتوي على وظيفتنا.

طريقة SpyOn() هي طريقة وهمية، لذا يجب إعادة ضبط حالتها. توصي وثائق Jest SpyOn() بإعادة ضبط الحالة باستخدام jest.restoreAllMocks() في رد اتصال afterEach()، وهو ما فعلناه أعلاه. إذا لم نفعل ذلك، فسيعود النموذج الوهمي غير محدد في الاختبار التالي بعد استدعاء SpyOn().

السخرية من وحدات ES

يمكن أن تحتوي وحدات ES على عمليات تصدير افتراضية ومسمى:

// esmModule.js

export default function () {
  return "original default";
}

export function named() {
  return "original named";
}

إليك ما ستبدو عليه اختبارات الملف أعلاه:

import * as esmModule from "./esmModule";

afterEach(() => {
  jest.restoreAllMocks();
});

it("can override the implementation for a single test", () => {
  jest
    .spyOn(esmModule, "default")
    .mockImplementation(() => "mock implementation default");
  jest
    .spyOn(esmModule, "named")
    .mockImplementation(() => "mock implementation named");

  expect(esmModule.default()).toBe("mock implementation default");
  expect(esmModule.named()).toBe("mock implementation named");

  expect(esmModule.default.mock.calls).toHaveLength(1);
  expect(esmModule.named.mock.calls).toHaveLength(1);
});

it("can override the return value for a single test", () => {
  jest.spyOn(esmModule, "default").mockReturnValue("mock return value default");
  jest.spyOn(esmModule, "named").mockReturnValue("mock return value named");

  expect(esmModule.default()).toBe("mock return value default");
  expect(esmModule.named()).toBe("mock return value named");

  expect(esmModule.default.mock.calls).toHaveLength(1);
  expect(esmModule.named.mock.calls).toHaveLength(1);
});

it("returns the original implementation when no overrides exist", () => {
  expect(esmModule.default()).toBe("original default");
  expect(esmModule.named()).toBe("original named");
});

كيف يعمل

يبدو هذا تقريبًا مثل مثال CommonJS السابق، مع بعض الاختلافات الرئيسية.

أولاً، نقوم باستيراد الوحدة الخاصة بنا كاستيراد مساحة اسم.

import * as esmModule from "./esmModule";

ثم عندما نريد التجسس على التصدير الافتراضي، نستخدم "الافتراضي":

  jest
    .spyOn(esmModule, "default")
    .mockImplementation(() => "mock implementation default");

استكشاف أخطاء استيراد وحدة ES وإصلاحها

في بعض الأحيان عند محاولة الاتصال بـ jest.spyOn() باستخدام حزمة جهة خارجية، سوف تحصل على خطأ مثل الخطأ أدناه:

    TypeError: Cannot redefine property: useNavigate
        at Function.defineProperty ()

عندما تواجه هذا الخطأ، ستحتاج إلى الاستهزاء بالحزمة التي تسبب المشكلة:

import * as reactRouterDOM from "react-router-dom";

// ADD THIS:
jest.mock("react-router-dom", () => {
  const originalModule = jest.requireActual("react-router-dom");

  return {
    __esModule: true,
    ...originalModule,
  };
});

afterEach(() => {
  jest.restoreAllMocks();
});

يستبدل هذا الرمز الوحدة النمطية بنموذج Jest ES Module الذي يحتوي على جميع الخصائص الأصلية للوحدة باستخدام معلمة المصنع الخاصة بـ jest.mocks. الخاصية __esModule مطلوبة عند استخدام معلمة المصنع في jest.mock للسخرية من وحدة ES (docs).

إذا أردت، يمكنك أيضًا استبدال وظيفة فردية في معلمة المصنع. على سبيل المثال، سوف يقوم React Router بإلقاء خطأ إذا قام المستهلك باستدعاء useNavigate() خارج سياق جهاز التوجيه، لذلك يمكننا استخدام jest.mock() لاستبدال تلك الوظيفة في ملف الاختبار بأكمله إذا أردنا:

jest.mock("react-router-dom", () => {
  const originalModule = jest.requireActual("react-router-dom");

  return {
    __esModule: true,
    ...originalModule,

    // Dummy that does nothing.
    useNavigate() {
      return function navigate(_location) {
        return;
      };
    },
  };
});

التفاف

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

أخبرني إذا فاتني شيء ما أو إذا كان هناك شيء لم أدرجه في هذا المنشور فيجب أن يكون هنا.

بيان الافراج تم إعادة نشر هذه المقالة على: https://dev.to/tylerlwsmith/override-functions-in-individual-tests-using-jest-dp5?1 إذا كان هناك أي انتهاك، يرجى الاتصال بـ [email protected] لحذفه
أحدث البرنامج التعليمي أكثر>

تنصل: جميع الموارد المقدمة هي جزئيًا من الإنترنت. إذا كان هناك أي انتهاك لحقوق الطبع والنشر الخاصة بك أو الحقوق والمصالح الأخرى، فيرجى توضيح الأسباب التفصيلية وتقديم دليل على حقوق الطبع والنشر أو الحقوق والمصالح ثم إرسالها إلى البريد الإلكتروني: [email protected]. سوف نتعامل مع الأمر لك في أقرب وقت ممكن.

Copyright© 2022 湘ICP备2022001581号-3