Функция обратного вызова, не охваченная тестом — как издеваться?

#javascript #angular #typescript #unit-testing #jestjs

Вопрос:

Я тестирую следующий метод:

   startScriptLoad(): void {
    const documentDefaultView = this.getDocumentDefaultView();
    if (documentDefaultView) {
      const twitterData: ICourseContentElementEmbedTweetWidgetData = this.getTwitterWidgetData() ?? {
        // eslint-disable-next-line @typescript-eslint/no-empty-function
        ready: () => {},
        _e: [],
      };
      if (this.scriptExists()) {
        ((this.document.defaultView as unknown) as ICourseContentElementEmbedTweetDocument)[
          this.TWITTER_OBJECT
        ] = twitterData;
        return;
      }
      this.appendScriptToDOM();
      twitterData._e = [];
      twitterData.ready = (callback: () => void) => {
        twitterData._e.push(callback);
      };
      ((this.document.defaultView as unknown) as ICourseContentElementEmbedTweetDocument)[
        this.TWITTER_OBJECT
      ] = twitterData;
    }
  }
 

с помощью следующего теста:

   describe('startScriptLoad()', () => {
    it('should load script', () => {
      jest.spyOn(service, 'getDocumentDefaultView');
      jest.spyOn(service, 'appendScriptToDOM');
      service.startScriptLoad();
      expect(service.getDocumentDefaultView).toHaveBeenCalled();
      expect(service.appendScriptToDOM).toHaveBeenCalled();
    });
  });
 

По какой-то причине я не могу получить покрытие для следующих строк кода в startScriptLoad():

   twitterData.ready = (callback: () => void) => {
    twitterData._e.push(callback);
  };
 

Есть ли способ каким-то образом имитировать вызов метода обратного вызова?

Ответ №1:

Используйте прокси-сервер для создания прокси-сервера для издевательского объекта, который может перехватывать и переопределять основные операции для этого объекта. Это означает, что мы можем использовать handler.set (), чтобы настроить ловушку для установки ready частного метода на mTwitterData объекте.

Ловушка заключается в следующем: назначьте ready анонимный метод переменной типа _ready . После его получения вызовите его вручную для последующего тестирования.

service.ts :

 export class SomeService {
  getTwitterWidgetData() {
    return {} as any;
  }
  startScriptLoad(): void {
    const twitterData = this.getTwitterWidgetData();
    twitterData._e = [];
    twitterData.ready = (callback: () => void) => {
      twitterData._e.push(callback);
    };
  }
}
 

service.test.ts :

 import { SomeService } from './service';

describe('69041800', () => {
  test('should pass', () => {
    const service = new SomeService();
    let _ready;
    const mTwitterData = new Proxy({} as any, {
      set: (obj, prop, value) => {
        if (prop === 'ready') {
          _ready = value;
        }
        obj[prop] = value;
        return true;
      },
    });
    const getTwitterWidgetDataSpy = jest.spyOn(service, 'getTwitterWidgetData').mockReturnValue(mTwitterData);
    service.startScriptLoad();
    expect(getTwitterWidgetDataSpy).toBeCalledTimes(1);
    // test ready private method
    const mCallback = jest.fn();
    _ready(mCallback);
    expect(mTwitterData._e).toEqual([mCallback]);
    getTwitterWidgetDataSpy.mockRestore();
  });
});
 

результат теста:

  PASS  examples/69041800/service.test.ts (9.519 s)
  69041800
    ✓ should pass (4 ms)

------------|---------|----------|---------|---------|-------------------
File        | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
------------|---------|----------|---------|---------|-------------------
All files   |   83.33 |      100 |   66.67 |   83.33 |                   
 service.ts |   83.33 |      100 |   66.67 |   83.33 | 3                 
------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        10.148 s