Jest: توفير واجهة برمجة تطبيقات لمسح قائمة انتظار حل الوعد

تم إنشاؤها على ٢٣ نوفمبر ٢٠١٦  ·  46تعليقات  ·  مصدر: facebook/jest

هل تريد طلب ميزة أو الإبلاغ عن خطأ ؟

_الميزة _ ، على ما أعتقد ، لكنها مهمة جدًا عند اختبار الكود الذي يستخدم Promise s.

ما هو السلوك الحالي؟

لدي مكون يستخدم Promise التفاف وتسلسل داخليًا في متابعة إجراء خارجي غير متزامن. أنا أقدم محاكاة للإجراء غير المتزامن وأفي بالوعد الذي يعود به في الاختبار.

المكون شيء مثل هذا:

class Component extends React.Component {
  // ...
  load() {
    Promise.resolve(this.props.load())
      .then(
        result => result
          ? result
          : Promise.reject(/* ... */)
        () => Promise.reject(/* ... */)
      )
      .then(result => this.props.afterLoad(result));
  }
}

ويظهر رمز الاختبار على النحو التالي:

const load = jest.fn(() => new Promise(succeed => load.succeed = succeed));
const afterLoad = jest.fn();
const result = 'mock result';
mount(<Component load={load} afterLoad={afterLoad} />);
// ... some interaction that requires the `load`
load.succeed(result);
expect(afterLoad).toHaveBeenCalledWith(result);

فشل الاختبار لأنه تم تقييم expect() قبل معالجات الوعد المقيدة. لا بد لي من تكرار طول سلسلة الوعد الداخلية في الاختبار للحصول على ما أحتاجه ، مثل هذا:

return Promise.resolve(load.succeed(result))
  // length of the `.then()` chain needs to be at least as long as in the tested code
  .then(() => {})
  .then(() => expect(result).toHaveBeenCalledWith(result));

ما هو السلوك المتوقع؟

أتوقع أن تقدم Jest نوعًا من واجهة برمجة التطبيقات (API) لمسح جميع معالجات الوعد المعلقة ، على سبيل المثال:

load.succeed(result);
jest.flushAllPromises();
expect(result).toHaveBeenCalledWith(result);

لقد حاولت runAllTicks و runAllTimers بلا جدوى.


_ بدلاً من ذلك ، إذا كنت أفتقد بعض الميزات أو النمط الموجود بالفعل ، آمل أن يوجهني أحد الأشخاص إلى الاتجاه الصحيح:) _

Enhancement New API proposal

التعليق الأكثر فائدة

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

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

مع انتظار غير متزامن يكاد يكون جميلًا:

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

ال 46 كومينتر

بينما يعد الاختبار غير المتزامن ، من الجيد أن تتذكر أنه يمكنك إعادة وظيفة الاختبار كوعد ، لذلك سيعمل شيء كهذا:

test('my promise test', () => { //a test function returning a Promise
  return Promise.resolve(load.succeed(result))
    .then(() => {})
    .then(() => expect(result).toHaveBeenCalledWith(result));
})

إن إرجاع الوعد من وظيفة الاختبار يجعل Jest تدرك أن هذا اختبار غير متزامن والانتظار حتى يتم حله أو انتهاء مهلته.

thymikee بالطبع .then(() => {}) في شفرتك. لا أرى كيف يمكنني وصف المشكلة بإيجاز أكثر مما فعلته في المنشور الافتتاحي. يرجى قراءتها بإسهاب وإما إعادة فتح المشكلة أو وصف كيفية حلها.

_ لقد أضفت return إلى الكود في OP لتجنب الالتباس ._

دخلت في مشكلة مماثلة ، ووصفها هنا: https://github.com/pekala/test-problem-example

باختصار: أحاول التأكيد على تسلسل الإجراءات المرسلة إلى متجر Redux كنتيجة لتفاعل المستخدم (تمت محاكاته باستخدام الإنزيم). الإجراءات التي تم إرسالها تزامنًا وغير متزامنة باستخدام الوعود (سخرت من حلها على الفور). يبدو أنه لا توجد طريقة للتأكيد بعد نفث سلسلة الوعد ، إذا لم يكن لديك وصول مباشر إلى سلسلة الوعد. setTimeout(..., 0) يعمل ، لكنه يشعر بالاختراق وإذا فشل التأكيد في رد الاتصال setTimeout ، يفشل Jest مع خطأ انتهاء المهلة (بدلاً من خطأ التأكيد).

فكرة flushAllPromises تبدو كحل ، على الرغم من أنني أعتقد أن هذا ما يجب أن يفعله runAllTicks ؟

كمتابعة: حاولت استبدال setTimeout(..., 0) بـ setImmediate ويبدو أن كلاهما يقوم بتشغيل التأكيدات بعد استنفاد قائمة انتظار المهام الدقيقة لرد الاتصال في Promise ويمنع Jest من انتهاء مهلة أخطاء التأكيد. لذلك ، هذا يعمل بشكل جيد ، وهو حل مقبول لحالة الاستخدام الخاصة بي:

test('changing the reddit downloads posts', done => {
    setImmediate(() => {
        // assertions...
        done()
    })
})

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

function flushPromises() {
  return new Promise(resolve => setImmediate(resolve));
}

test('', () => {
  somethingThatKicksOffPromiseChain();
  return flushPromises().then(() => {
    expect(...
  });
})

مع انتظار غير متزامن يكاد يكون جميلًا:

test('', async () => {
  somethingThatKicksOffPromiseChain();
  await flushPromises();
  expect(...
})

jwbay هذا بعض السكر الجميل هناك!

صحيح أن هذا flushPromises يتضح أنه سطر واحد ، لكن ليس من الواضح على الإطلاق كيفية الوصول إلى هذا السطر الواحد. لذلك أعتقد أنه سيكون من المفيد لمستخدمي Jest إتاحتها كوظيفة استخدام.

pekala the one liner IMO لا يوفر السلوك المطلوب لأنه لن ينتظر حتى يتم حل الوعد المعلق التالي:

function foo() {  
  return new Promise((res) => {
    setTimeout(() => {
      res()
    }, 2000);
  });
}

ماذا عن الوعد العنيف وعندما يتم إنشاء وعد جديد ، قم بإضافته إلى بعض المصفوفات ثم دفع جميع الوعود التي تنتظر Promise.all على هذه المجموعة؟

talkol أعتقد أنه سيكون كذلك ، طالما أنك لنا

pekala لا داعي لتزييف أجهزة ضبط الوقت مع هذا المثال لأن الوعد لن
أنا قلق فقط من أن الوعد العنيف سوف يفسد الأعمال الداخلية المزحة ، إنه قلب صلب بعض الشيء

إذا لم تقم بتزييف أجهزة ضبط الوقت ، فستستغرق اختباراتك 2 ثانية + حتى تكتمل. أعتقد أن أفضل ممارسة هي إزالة هذه الأنواع من التأخيرات ، وفي هذه الحالة يقوم flushPromises كما اقترحه jwbay بهذه المهمة.

كل هذا يتوقف على ما تحاول اختباره :) كل ما أقوله هو أن المؤقتات مصدر قلق لا علاقة له بانتظار الوعود

نحن نواجه مشكلات تتعلق بعدم حل الوعود ، والتي تختلط مع مكالمات setTimeout. في الإصدار 19.0.2 من jest ، ليست لدينا أية مشكلات ، ولكن في الإصدار 20.0.0 من jest ، لا تدخل الوعود أبدًا وظائف الحل / الرفض ، وبالتالي تفشل الاختبارات. يبدو أن مشكلتنا تتعلق بهذه المشكلة المتمثلة في عدم وجود واجهة برمجة تطبيقات لمسح قائمة انتظار حل الوعد_ ، ولكن يبدو أن هذه المشكلة تسبق الإصدار 20.0.0 من jest حيث بدأنا في رؤية المشكلة ، لذلك لست متأكدًا تمامًا.

هذا هو الحل الوحيد الذي تمكنا من التوصل إليه لبعض اختباراتنا ، نظرًا لأن لدينا سلسلة من setTimeout s و Promise s المستخدمة في الكود الذي يستدعي في النهاية رد الاتصال onUpdateFailed .

  ReactTestUtils.Simulate.submit(form);
  return Promise.resolve()
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => { jest.runOnlyPendingTimers(); })
    .then(() => {
      expect(onUpdateFailed).toHaveBeenCalledTimes(1);
      expect(getErrorMessage(page)).toEqual('Input is invalid.');
    });

ليست جميلة ، لذا فإن أي نصيحة هنا موضع تقدير كبير.

مثال آخر حيث لا يمكنك إرجاع الوعد من الاختبار:

describe('stream from promise', () => {
  it('should wait till promise resolves', () => {
    const stream = Observable.fromPromise(Promise.resolve('foo'));
    const results = [];
    stream.subscribe(data => { results.push(data); });
    jest.runAllTimers();
    expect(results).toEqual(['foo']);
  });
});

فشل هذا الاختبار مع مازحة 20.0.4.

يمكن أيضًا كتابة حل philwhln بغير التزامن / انتظار

ReactTestUtils.Simulate.submit(form);

await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();
await jest.runOnlyPendingTimers();

expect(onUpdateFailed).toHaveBeenCalledTimes(1);
expect(getErrorMessage(page)).toEqual('Input is invalid.');

أحب وظيفة المرافق التي طغت قائمة انتظار الوعد

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

أنا أختبر الكود الذي يستخدم Promise.all للوفاء بوعود متعددة. عندما يفشل أحد تلك الوعود المغلفة (لأن هذا ما أريد اختباره) يعود الوعد على الفور مما يعني أن الوعود الأخرى في بعض الأحيان (حالة السباق ، غير الحتمية) تعود أثناء الاختبار التالي.

يتسبب هذا في كل أنواع الخراب مع نتائج اختباراتي غير المتوقعة / القابلة للتكرار.

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

هناك بالفعل واجهة برمجة تطبيقات لمسح المهام الصغيرة في قائمة الانتظار بـ process.nextTick ومن المحتمل أن تعمل API أيضًا مع Promises ( jest.runAllTicks ).

كان لدي حل مع الياسمين الذي تم ربطه بـ NextTick من Yaku ، مكتبة الوعد واشتعلت المكالمات التالية وسمح لها باللعب في وقت مبكر.
مهما كانت الدعابة تستخدم الوعود نفسها ، مما جعل هذه مشكلة.
في النهاية ، أخذت Yaku واخترقته للحصول على طريقة تدفق تطرد قائمة الانتظار الخاصة به. بشكل افتراضي ، يتم تشغيله بشكل طبيعي باستخدام nextTick ، ​​ولكن إذا قمت باستدعاء flush ، فسيتم تنفيذ جميع معالجات الوعد المعلقة.
المصدر هنا:
https://github.com/lukeapage/yaku-mock
يمكن أن تفعل ذلك من خلال الترتيب ، والاتصال بـ ysmood لمعرفة ما يفكرون فيه وإضافة الوثائق ، لكنها إلى حد كبير تفعل ما تريده وعملت معي كحل بسيط لتقديم الوعود المتزامنة في الاختبارات.

كحل بسيط لذلك ، أحب حل jwbay .

ماذا عن إضافة شيء مشابه للكائن jest ؟

await jest.nextTick();

نفذت باسم

const nextTick = () => new Promise(res => process.nextTick(res));

سم مكعبcpojerSimenBrogeliog

أنا أستخدم الإنزيم لتركيب مكونات React.

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

هذا هو الحل الذي انتهى بي الأمر به باستخدام جاسوس على وظيفة الوعد العالمية.

global.Promise = require.requireActual('promise');

it('my test', async () => {
    const spy = sinon.spy(global, 'Promise');

    wrapper.props().dispatch(functionWithPromiseCalls());

    for (let i = 0; i < spy.callCount; i += 1) {
      const promise = spy.getCall(i);
      await promise.returnValue;
    }

    expect(...)
});

واجهت حالة استخدام لهذا (شكرًا jwbay على التقنية الرائعة)

على سبيل المثال ، تريد التحقق من أن وظيفتك بها مهلة ، وأن المهلة يتم فرضها بدقة:

      jest.useFakeTimers();
      const EXPECTED_DEFAULT_TIMEOUT_MS = 10000;

      const catchHandler = jest.fn().mockImplementationOnce(err => {
        expect(err).not.toBeNull();
        expect(err.message).toContain('timeout');
      });

      // launch the async func returning a promise
      fetchStuffWithTimeout().catch(catchHandler);

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(EXPECTED_DEFAULT_TIMEOUT_MS - 1);
      await flushPromises();

      expect(catchHandler).not.toHaveBeenCalled(); // not yet

      jest.advanceTimersByTime(1);
      await flushPromises();

      expect(catchHandler).toHaveBeenCalledTimes(1); // ok, rejected precisely

لا تسمح إعادة الوعد بالتحقق من التوقيت الدقيق للقرار / الرفض.

هناك حاجة إلى الوعد بيغ. بدونها ، يتم استدعاء التوقع مبكرًا جدًا.

أتمنى أن يساعد هذا في تضييق نطاق المشكلة.

للأشخاص الذين يتابعون ذلك ، هناك علاقات عامة مفتوحة لهذا هنا: # 6876

عبر النشر من https://github.com/airbnb/enzyme/issues/1587

أتساءل عما إذا كان يجب أن يكون النمط التالي كافيًا لحل هذه المشكلة ، وإذا كنت أفعل شيئًا يعتبر ممارسة سيئة ولا ينبغي أن أفعله.

ما رأي الناس في هذا النهج؟

export class MyComponent extends React.Component {
  constructor (props) {
    super(props)

    this.hasFinishedAsync = new Promise((resolve, reject) => {
      this.finishedAsyncResolve = resolve
    })
  }

  componentDidMount () {
    this.doSomethingAsync()
  }

  async doSomethingAsync () {
    try {
      actuallyDoAsync()
      this.props.callback()
      this.finishedAsyncResolve('success')
    } catch (error) {
      this.props.callback()
      this.finishedAsyncResolve('error')
    }
  }

  // the rest of the component
}

وفي الاختبارات:

it(`should properly await for async code to finish`, () => {
  const mockCallback = jest.fn()
  const wrapper = shallow(<MyComponent callback={mockCallback}/>)

  expect(mockCallback.mock.calls.length).toBe(0)

  await wrapper.instance().hasFinishedAsync

  expect(mockCallback.mock.calls.length).toBe(1)
})

واجهت مشكلة عندما لم يتم إجراء المكالمة غير المتزامنة مباشرة في componentDidMount ، لكنها كانت تستدعي وظيفة غير متزامنة ، والتي كانت تستدعي وظيفة غير متزامنة أخرى وما إلى ذلك. إذا أضفت خطوة غير متزامنة إضافية في كل السلسلة غير المتزامنة ، فسأحتاج إلى إضافة .then() أو await إضافي ، لكن هذا يعمل بشكل جيد.

هل هناك سبب يمنعني من استخدام هذا النهج أم أن هذا يبدو جيدًا للناس؟

ذهبت في مغامرة للقيام بذلك في أرض المستخدم ووجدت أنه ممكن فعلاً وليس سيئًا للغاية (على الرغم من وجود عدد غير قليل من المزالق التي يمكن مواجهتها إذا لم يكن لديك خريطة). إليك تقرير تجربة يكون (نأمل) مفصلاً بدرجة كافية لاستخدامه مباشرةً ؛ TLDR هو تحويل async / await إلى الوعود ، ومبادلة الوعود الأصلية بـ bluebird وأجهزة ضبط الوقت الأصلية لـ lolex ؛ ترجمة كل شيء ، بما في ذلك node_modules/ ؛ queueMicrotask هو العنصر البدائي الذي تحتاجه للوعود ، ولكن بشكل افتراضي لن توفره lolex نظرًا لعدم قيام JSDOM بتوفيره.

واجهت نفس المشكلة مع jest.mockAllTimers() ومكونات React التي تستدعي Promise في componentDidMount() .

الحل من # issuecomment-279171856 حل المشكلة بطريقة أنيقة.

نحتاج إلى شيء مشابه في Jest API الرسمية!

لقد واجهت مؤخرًا مشكلة أثناء ترقية مجموعة من الأشياء ، فقد كشفت عن مشكلة في مجموعة من الاختبارات حيث لم نكن ننتظر دائمًا انتهاء الوعود. وبينما نجحت طرق مثل await new Promise(resolve => setImmediate(resolve)); في حالات بسيطة ، وجدت في اختباراتي ، أنه كان علي تشغيل ذلك عدة مرات لمسح الأنبوب. وهو ما ذكره quasicomputational في استكشافهم هنا . لسوء الحظ ، لا أعتقد أن هناك طريقة لمعرفة متى يكون هذا الأنبوب واضحًا دون اعتراض الوعود عند إنشائها. لذلك أنشأت مكتبة صغيرة للقيام بذلك ... جاسوس الوعد . رغم ذلك ، كان لدي اختبار واحد كان يستخدم مؤقتًا مزيفًا ولم ينجح مع ذلك ... لذا فهو ليس حلاً عمليًا بالكامل بعد.

على الرغم من أنني أتخيل أيضًا أنهم قد يعملون فقط مع async / await alls في التعليمات البرمجية الخاصة بك ليتم اختبارها إذا تم تحويلها إلى وعود. إذا لم يتم تحويلها إلى وعود ، فلن تتمكن هذه المكتبة من ربطها وانتظار اكتمالها.

وجدت نفسي أعاني من نفس المشكلة وأدركت:
يجب ألا نفشل بالوعود المعلقة ولكن بدلاً من ذلك يجب أن نفشل الاختبار بأكمله إذا كانت هناك وعود معلقة.
بهذه الطريقة سنضطر إلى إحباط الوعود المعلقة داخل الكود الذي تم اختباره باستخدام وحدة التحكم في الإلغاء:
https://developers.google.com/web/updates/2017/09/abortable-fetch
إن الحصول على الوعود المزاحمة يساوي القول "التزامن صعب ، لذا دعونا لا نختبره". في الواقع ، يجب أن يكون الأمر عكس ذلك تمامًا.
نظرًا لأن التزامن صعب ، يجب علينا اختباره أكثر وعدم السماح للاختبار على الإطلاق بالوعود المعلقة.

بالنظر إلى الفوضى المتعلقة بإحباط الوعود في سؤال Stackoverflow هذا ، فمن الواضح أنه ليس (حتى الآن) أمرًا سهلاً للقيام به:
https://stackoverflow.com/a/53933849/373542
سأحاول كتابة تطبيق KISS لإجهاض وعود الجلب الخاصة بي وسأنشر النتيجة هنا.

@ giorgio-zamparelli: _ "التزامن صعب ، لذا دعونا لا نختبره" _ يتعدى تماما موضوع التقرير الأصلي. لا يتعلق الأمر بالوعود "المعلقة" ولكن بالأحرى بحقيقة أن انتظار نشر الوعد _ الحل_ من خلال الشفرة غير المتزامنة في الاختبارات أمر صعب بلا داع.

أعتقد أن الوفاء بالوعود سيكون علاجًا للأعراض بدلاً من المرض.

يجب أن تحل الوعود بشكل طبيعي في الاختبارات دون الحاجة إلى تفريغها.
إذا كان هناك وعد معلق في اختبارك ، فيجب عليك إما الانتظار حتى يتم حله باستخدام على سبيل المثال wait من @testing-library/react أو إذا لم يكن الوعد المعلق جزءًا من نطاق الاختبار ، فيجب عليك إما اسخر من الكود الذي يبدأ به أو يجب عليك إحباط الوعد المعلق في مكان ما مثل حدث دورة حياة React willUnmount باستخدام AbortController

AbortController هي واجهة برمجة تطبيقات جديدة لا يستخدمها أي شخص تقريبًا ولدي شعور بأنه الحل لمعظم الوعود المعلقة في الاختبارات.

تثبت لي خطأ:
يمكن أن أكون مخطئًا بسهولة إذا كان شخص ما أبلغ عن وجود مشاكل في هذه المشكلة قد حاول بالفعل استخدام AbortController و jest.mock ولم يكن ذلك كافيًا.

@ giorgio-zamparelli: ربما ينبع سوء التفاهم من استخدامي لعبارة _ "فلوش جميع معالجات الوعد المعلقة" _ (وإذا حدث ذلك ، فأنا آسف). كما سترى ما إذا كنت قد قرأت وصف المشكلة جيدًا ، فأنا أعني "معالجي الوعود المعلقة".

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

لقد أصدرت مؤخرًا flush-microtasks لهذا الغرض. انها تقترض تنفيذه من ردة الفعل، وهو أمر يثير الدهشة أكثر تعقيدا منjwbay الصورة الحل هنا أوthymikee "حل الصورة هنا . لست متأكدًا مما إذا كان التعقيد يحدث أي فرق ذي مغزى ، لكنني أفترض أنه يفسر حالات الحافة التي لم يتم أخذها في الاعتبار بواسطة الحلول الأخرى في هذا الموضوع. لقد استخدمت هذا التطبيق فقط لأن react-testing-library يستخدمه (انظر هنا ) ، لكن لا يعرضه.

import { flushMicroTasks } from 'flush-microtasks'

await flushMicroTasks()

aleclarson هل هناك أي فرق بين مهام التنظيف الدقيق ووعود

ramusus يبدو أن flush-promises يستخدم نفس الأسلوب المستخدم في حل

https://github.com/kentor/flush-promises/blob/46f58770b14fb74ce1ff27da00837c7e722b9d06/index.js

قامت RTL أيضًا بنسخ كود React: https://github.com/testing-library/react-testing-library/blob/8db62fee6303d16e0d5c933ec1fab5841dd2109b/src/flush-microtasks.js

تحرير: هاه ، سبق ذكره: ابتسامة عريضة:

لست متأكدًا من أننا بحاجة إلى دمجها في Jest عندما يمكن للناس استخدام ذلك؟ ربما يمكننا الارتباط به في المستندات؟ تتعلق هذه المشكلة بمسحها بشكل متزامن ، وهو ما أعتقد أنه يتجاوز ما نريد القيام به (خاصة أنه مستحيل مع async-await )

الحل flushPromises يعمل فقط على الوعود التي يتم حلها على الفور ولكن ليس على تلك التي لا تزال معلقة.

حسنًا ، نقطة جيدة. لا أعرف ما إذا كان من الممكن تتبع وعود pending بطريقة أو بأخرى. قد يكون قادرًا على فعل شيء ذكي باستخدام async_hooks ، لست متأكدًا. من المحتمل أن تكون محاولة التمييز بين الوعود التي تم إنشاؤها بواسطة رمز Userland والوعود التي أنشأتها Jest وتوابعها أمرًا مؤلمًا

لقد حاولت التفاف / السخرية من الكائن Promise لتضمين عداد ولكن هذا لا يعمل:

const _promise = window.Promise;
window.Promise = function(promiseFunction){
    // counter
    return new _promise(promiseFunction);
}

المشكلة الرئيسية هي وظائف async التي لا تستخدم Promise على الإطلاق

حسنًا ، لقد وجدت حقًا طريقة مخترقة مثل هذه .

  1. إنشاء وحدة جديدة مع قائمة.
  2. أضف وعودك إلى تلك القائمة.
  3. قم بحسم الوعود في اختبارك وقم بإزالتها من القائمة.
  4. في حالتي ، قمت بتشغيل wrapper.update() من إنزيم. افعل هنا شيئًا مشابهًا إذا لزم الأمر.
  5. كرر الخطوتين 3 و 4 حتى تصبح القائمة فارغة.

أعلم أنه ليس من الممارسات الجيدة ضبط الكود على الاختبارات ولكني أستخدم هذا المنطق بالفعل في العرض من جانب الخادم. لكنها في النهاية تنتظر فقط. ¯ \ _ (ツ) _ / ¯

هناك تحديث مثير للاهتمام لهذا في Jest 26 ، حيث تعتمد أجهزة ضبط الوقت المزيفة الآن على @ sinon / fake-timers (إذا تم تمكينها بـ jest.useFakeTimers('modern') ).

لقد جربت أجهزة ضبط الوقت المزيفة الحديثة مع اختباراتي ، ولسوء الحظ تسبب هذا الاختراق await new Promise(resolve => setImmediate(resolve)); التعطل إلى أجل غير مسمى. لحسن الحظ ، يتضمن @sinon/fake-timers عدة طرق *Async() والتي "ستكسر أيضًا حلقة الحدث ، مما يسمح بتنفيذ أي عمليات رد نداء مجدولة _before_ تشغيل المؤقتات.". لسوء الحظ ، لا أرى أي طريقة للحصول على الكائن clock عبر Jest APIs.

هل يعرف أي شخص كيف يجعل Jest يعطينا هذا العنصر clock ؟

مثل الآخرين ، فإن حافزي لاستخدام await new Promise(setImmediate); هو الوفاء بالوعود القابلة للحل ، حتى أتمكن من اختبار تأثيرها على النظام.

قد يبدو أن أجهزة ضبط الوقت المزيفة "الحديثة" أقل من أداء الآخرين من خلال التوقيت الذي يبدو بلا معنى.

إليك بعض اختبارات الوحدة لوصف هذا:

describe('flushing of js-queues using different timers', () => {
  beforeAll(() => {
    // It would take the failing test 5 long seconds to time out.
    jest.setTimeout(100);
  });

  it.each([
    [
      'given real timers',
      () => {
        jest.useRealTimers();
      },
    ],
    ['given no timers', () => {}],
    [
      'given "legacy" fake timers',
      () => {
        jest.useFakeTimers('legacy');
      },
    ],
    [
      // This is the the failing scenario, not working like the other timers.
      'given "modern" fake timers',
      () => {
        jest.useFakeTimers('modern');
      },
    ],
  ])(
    '%s, when using setImmediate to flush, flushes a promise without timing out',
    async (_, initializeScenarioSpecificTimers) => {
      initializeScenarioSpecificTimers();

      let promiseIsFlushed = false;

      Promise.resolve().then(() => {
        promiseIsFlushed = true;
      });

      // Flush promises
      await new Promise(setImmediate);

      expect(promiseIsFlushed).toBe(true);
    },
  );
});

أشعر أن الاختبار السابق يجب ألا يفشل كما هو.

بالنسبة لي ، كان الحل هو مسح الوعود باستخدام "setImmediate" العقدة الأصلية من الحزمة "timers" ، بدلاً من "setImmediate" العالمية. بعد هذا ، يمر ما يلي:

import { setImmediate as flushMicroTasks } from 'timers';

it('given "modern" fake timers, when using native timers to flush, flushes a promise without timing out', async () => {
  jest.useFakeTimers('modern');

  let promiseIsFlushed = false;

  Promise.resolve().then(() => {
    promiseIsFlushed = true;
  });

  // Flush micro and macro -tasks
  await new Promise(flushMicroTasks);

  expect(promiseIsFlushed).toBe(true);
});

شكراaleclarson.

إليك الحل الذي نقدمه لهذه المشكلة:

https://github.com/team-igniter-from-houston-inc/async-fn
https://medium.com/houston-io/how-to-unit-test-asynchronous-code-for-javascript-in-2020-41c124be2552

يمكن كتابة كود الاختبار مثل:

// Note: asyncFn(), extends jest.fn() with a way to control resolving/rejecting of a promise
const load = asyncFn();

const afterLoad = jest.fn();
const result = 'mock result';

mount(<Component load={load} afterLoad={afterLoad} />);

// ... some interaction that requires the `load`

// Note: New way to controlling when promise resolves
await load.resolve(result);

expect(afterLoad).toHaveBeenCalledWith(result);

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

jansav لطيف / + 1. لقد رأيت أن هذا النهج يسمى النمط المؤجل. أعتقد أنه يجعل الاختبارات أجمل.

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

حالة الاستخدام الخاصة بي:

public static resolvingPromise<T>(result: T, delay: number = 5): Promise<T> {
    return new Promise((resolve) => {
        setTimeout(
            () => {
                resolve(result);
            },
            delay
        );
    });
}

ملف الاختبار:

it("accepts delay as second parameter", async () => {
    const spy = jest.fn();
    MockMiddleware.resolvingPromise({ mock: true }, 50).then(spy);
    jest.advanceTimersByTime(49);
    expect(spy).not.toHaveBeenCalled();
    jest.advanceTimersByTime(1);
    await Promise.resolve(); // without this line, this test won't pass
    expect(spy).toHaveBeenCalled();
});
هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات