جاوااسکریپت - تفاوت بین resetAllMocks، resetModules، resetModuleRegistry، restoreAllMocks در Jest


من سعی دارم مفاهیم زیر را در Jest درک کنم:

resetAllMocks، resetModules، resetModuleRegistry و restoreAllMocks

و من در حال سخت یافتن آن هستم.

من مستندات jest را خواندهام اما خیلی واضح نیست. ممنون میشوم اگر کسی بتواند لطفاً یک مثال از اینکه چگونه موارد بالا کار میکنند و با یکدیگر تفاوت دارند، ارائه دهد.

پاسخ شماره ۱

بخشهای زیر رفتار هر تابع و دستور پیکربندی مربوطه را توضیح میدهند. در مورد دستورهای پیکربندی، رفتار توضیح داده شده در بین هر آزمایش رخ میدهد و آنها را هر چه بیشتر از سایر آزمایشها جدا میکند.

ارجاعات به fn اشاره به یک تابع نمونهی ماک جست (jest mock) تحت هر یک از این اقدامات دارند.

jest.clearAllMocks() و clearMocks:[boolean]

تمام دادههای استفاده از ماکها را بازنشانی میکند، نه پیادهسازی آنها را. به عبارت دیگر، تنها ویژگیهای fn.mock.calls و fn.mock.instances یک تابع ماک jest را جایگزین میکند.

jest.resetAllMocks() و resetMocks:[boolean]

یک ابرمجموعه از clearAllMocks() که همچنین به بازنشانی پیادهسازی به یک تابع no return نیز میپردازد. به عبارت دیگر، این کار تابع mock را با یک jest.fn() جدید جایگزین میکند، نه فقط fn.mock.calls و fn.mock.instances آن.

jest.restoreAllMocks() و restoreMocks:[boolean]

مشابه resetAllMocks()، با یک تفاوت بسیار مهم. این تابع پیادهسازی اصلی «اسپایها» را بازیابی میکند. بنابراین، این طور است که «موکها را با jest.fn() جایگزین کن، اما اسپایها را با پیادهسازی اصلیشان جایگزین کن».

بنابراین، در مواردی که ما چیزها را به صورت دستی با jest.fn() اختصاص میدهیم (نه اسپایها)، خودمان باید مراقب بازیابی پیادهسازی باشیم چون jest این کار را انجام نخواهد داد.

jest.resetModules() و resetModules:[boolean]

این رجیستری ماژول Jest را که یک حافظه پنهان برای تمام ماژولهای مورد نیاز/وارد شده است، بازنشانی میکند. Jest پس از فراخوانی این، هر ماژول مورد نیاز را دوباره وارد خواهد کرد. تصور کنید یک صفحه سفید بدون اینکه مجبور باشید با تمام ماژولهای شبیهسازیشده در تستهای دیگر سروکار داشته باشید.

jest.resetModuleRegistry

این تنها یک نام مستعار برای resetModules است، ببینید:
https://github.com/facebook/jest/blob/7f69176c/packages/jest-runtime/src/index.ts#L1147


خلاصه

این جدول خلاصهای را نشان میدهد؛ ستون سمت چپ نوع آزمایشی که انجام میدهید است:

این آزمون را ریست خواهد کردclearAllMocks()بازنشانیتمامشبیهسازها()بازنشانیتمامشبیهسازها()بازنشانی_ماژولها()
expect(mock).toHaveBeenCalled...✓✓✓
mock.mockImplementation(...)✓✓
jest.spyOn(o,'f')✓
require('module').global++✓

ببینید که پاک کردن، بازنشانی و بازیابی در عمل چگونه متفاوت هستند:
https://repl.it/@sepehr/jest-mock-api-reset-restore#jest-mock-apis.test.js

PASS  ./jest-mock-apis.test.js

jest mock reset/restore api
  when calling mockReset() on a test double with custom impl.
    if the test double is a spy
      ✓ jest replaces the impl. to a new undefined-returning jest.fn() (18ms)
    if the test double is "not" a spy
      ✓ jest replaces the impl. to a new undefined-returning jest.fn() (17ms)

  when calling mockRestore() on a test double with custom impl.
    if the test double is "not" a spy
      ✓ jest resets the impl. to a new undefined-returning jest.fn() (2ms)
    if the test double is a spy
      ✓ jest restores the original impl. of that spy (7ms)

describe('jest mock reset/restore api', () => {
  
  describe('when calling mockReset() on a test double with custom impl.', () => {
    describe('if the test double is a spy', () => {
      test('jest replaces the impl. to a new undefined-returning jest.fn()', () => {
        const module = { api: () => 'actual' }
        jest.spyOn(module, 'api').mockImplementation(() => 'spy mocked')
        
        expect(module.api()).toStrictEqual('spy mocked')
        expect(module.api).toHaveBeenCalledTimes(1)

        module.api.mockReset()

        expect(module.api()).toStrictEqual(undefined)
        expect(module.api).toHaveBeenCalledTimes(1)
      })
    })
    

    describe('if the test double is "not" a spy', () => {
      test('jest replaces the impl. to a new undefined-returning jest.fn()', () => {
        const api = jest.fn(() => 'non-spy mocked')
        
        expect(api()).toStrictEqual('non-spy mocked')
        expect(api).toHaveBeenCalledTimes(1)

        api.mockReset()

        expect(api()).toStrictEqual(undefined)
        expect(api).toHaveBeenCalledTimes(1)
      })
    })
  })

  describe('when calling mockRestore() on a test double with custom impl.', () => {
    describe('if the test double is "not" a spy', () => {
      test('jest resets the impl. to a new undefined-returning jest.fn()', () => {
        const api = jest.fn(() => 'non-spy mocked')
        
        expect(api()).toStrictEqual('non-spy mocked')
        expect(api).toHaveBeenCalledTimes(1)

        api.mockRestore()

        expect(api()).toStrictEqual(undefined)
        expect(api).toHaveBeenCalledTimes(1)
      })
    })

    describe('if the test double is a spy', () => {
      test('jest restores the original impl. of that spy', () => {
        const module = { api: () => 'actual' }
        jest.spyOn(module, 'api').mockImplementation(() => 'spy mocked')
        
        expect(module.api()).toStrictEqual('spy mocked')
        expect(module.api).toHaveBeenCalledTimes(1)

        module.api.mockRestore()

        expect(module.api()).toStrictEqual('actual')
        expect(module.api).not.toHaveProperty('mock')
      })
    })
  })
})

پاسخ شماره ۲

ممنون از جواب @sepehr.
فکر میکنم با مثال راحتتر قابل فهم باشد.

نکات سریع:

  1. اگر میخواهید تعداد دفعات فراخوانی یک تابع شبیهسازی شده را آزمایش کنید، قبل از استفاده clear کنید
  2. اگر میخواهید مطمئن شوید مقدار بازگشتی ماک، سایر تستها را آلوده نمیکند، reset را فراخوانی کنید
  3. اگر میخواهید از روش اصلی به جای پیادهسازی شبیهسازی شده استفاده کنید، restore را فراخوانی کنید.
import {Calculator} from './calculator';

describe('calculator add', function () {
    let calculator = new Calculator();
    const mockAdd = jest.spyOn(calculator, 'add');
    it('mock the add method', function () {
        calculator.add = mockAdd.mockReturnValue(5);
        expect(calculator.add(1, 2)).toBe(5);
    });

    it('because we didnt clear mock, the call times is 2', function () {
        expect(calculator.add(1, 2)).toBe(5);
        expect(mockAdd).toHaveBeenCalledTimes(2);
    });

    it('After clear, now call times should be 1', function () {
        jest.clearAllMocks();
        expect(calculator.add(1, 2)).toBe(5);
        expect(mockAdd).toHaveBeenCalledTimes(1);
    });

    it('we reset mock, it means the mock has no return. The value would be undefined', function () {
        jest.resetAllMocks();
        expect(calculator.add(1, 2)).toBe(undefined);
    });

    it('we restore the mock to original method, so it should work as normal add.', function () {
        jest.restoreAllMocks();
        expect(calculator.add(1, 2)).toBe(3);
    });
});