首页 > 解决方案 > 测试中未涵盖的回调函数 - 如何模拟?

问题描述

我正在测试以下方法:

  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);
  };

有没有办法可以以某种方式模拟对回调方法的调用?

标签: javascriptangulartypescriptunit-testingjestjs

解决方案


使用Proxy为模拟对象创建一个代理,它可以拦截和重新定义该对象的基本操作。这意味着我们可以使用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

推荐阅读