使用玩笑创建模拟对象

Mock object creation using jest

我对玩笑测试还很陌生。我有以下实现 class

import { ExternalObject } from 'external-library';

export class MyClass {
  public createInstance(settings : ISettings) : ExternalObject {
    const setting1 = settings.getSetting("setting1");
    const setting2 = settings.getSetting("setting2");
    return new ExternalObject(setting1, setting2);
  }
}

我正在尝试测试这个 class 并且我已经能够正确模拟设置。但是我无法模拟外部对象构造 new ExternalObject(setting1, setting2); 并且我的测试用例失败了,因为它正在尝试构造实际对象(由于传递的参数实际上不是有效值而失败)。

describe("Create Instance", () => {  
  test("Allow creation of external instance", () => {
    // not sure if I am using this correctly? 
    // Aim is to mock out external module and any objects it may need creating
    jest.mock('external-library', () => {
      return {
        ExternalObject: jest.fn().mockImplementation()
      }
    });
    let settings: ISetting = new Settings();
    jest.spyOn(settings, "getSetting")
    .mockImplementationOnce(() => 'abcd')
    .mockImplementationOnce(() => 'xyz')

    let myImpl = new MyClass();
    let inst = myImpl.createInstance(settings);
    // expecting that the instance is created successfully.
    expect(inst).toBeTruthy();
  });
});

但是我不确定我在这里做错了什么。我确实浏览了文档和其他一些问题,但无法理解我遗漏了什么。

在函数范围内使用jest.mock(),模拟模块不会被提升到代码块的顶部。这将导致 external-library 模块在导入被测试的代码时不会被模拟。只需将 jest.mock() 移动到测试文件的模块范围。

例如

MyClass.ts:

//@ts-ignore
import { ExternalObject } from 'external-library';
import { ISettings } from './Settings';

export class MyClass {
  public createInstance(settings: ISettings): ExternalObject {
    const setting1 = settings.getSetting('setting1');
    const setting2 = settings.getSetting('setting2');
    return new ExternalObject(setting1, setting2);
  }
}

Settings.ts:

export interface ISettings {
  getSetting(key: string): void;
}

export class Settings implements ISettings {
  getSetting(key: string) {
    return 'real implementation';
  }
}

MyClass.test.ts:

import { MyClass } from './MyClass';
import { ISettings, Settings } from './Settings';
//@ts-ignore
import { ExternalObject } from 'external-library';

jest.mock(
  'external-library',
  () => {
    return { ExternalObject: jest.fn() };
  },
  { virtual: true }
);

describe('68545423', () => {
  afterEach(() => {
    jest.restoreAllMocks();
  });
  afterAll(() => {
    jest.resetAllMocks();
  });
  test('should pass', () => {
    let myImpl = new MyClass();
    let settings: ISettings = new Settings();
    jest
      .spyOn(settings, 'getSetting')
      .mockImplementationOnce(() => 'abcd')
      .mockImplementationOnce(() => 'xyz');

    let inst = myImpl.createInstance(settings);
    expect(inst).toBeTruthy();
    expect(ExternalObject).toBeCalledWith('abcd', 'xyz');
  });
});

测试结果:

 PASS  examples/68545423/MyClass.test.ts (7.233 s)
  68545423
    ✓ should pass (4 ms)

-------------|---------|----------|---------|---------|-------------------
File         | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s 
-------------|---------|----------|---------|---------|-------------------
All files    |   85.71 |      100 |      50 |   85.71 |                   
 MyClass.ts  |     100 |      100 |     100 |     100 |                   
 Settings.ts |      50 |      100 |       0 |      50 | 7                 
-------------|---------|----------|---------|---------|-------------------
Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        8.032 s, estimated 9 s
Ran all test suites related to changed files.