开玩笑:传递给构造函数的模拟依赖项

时间:2019-05-03 08:29:30

标签: node.js typescript unit-testing jestjs koa

我一直在尝试模拟通过构造函数传入的类,到目前为止,我所遇到的所有示例都已初始化构造函数内部的依赖项。 据我了解,玩笑用其重写的构造函数替换了这些依赖关系,但是由于我自己传递了依赖关系,因此在初始化UnderTest时需要一个可传递的实例。

对于理想的情况,我理想地是寻找一种类似于Mockito的行为。

const mockedDependency = ???
const underTest = new UnderTest(mockedDependency)

...
Proceed to write tests for underTest

这是我要测试的代码。请假设MockedDependency拥有自己的依赖项,并且也要传递给构造函数。

export default class UnderTest {

  private mockedDependency : MockedDependency

  constructor(mockedDependency: MockedDependency) {
    this.mockedDependency = mockedDependency
  }

  public methodUnderTest(parameter: string) {
    const mockedResult = this.mockedDependency.returnSomething(parameter)
    return this.doSomethingElse(mockedResult)
  }

  public methodUnderTest2(parameter1: string, parameter2: string) {
    const mockedResult = this.mockedDependency.returnSomething2(parameter1, parameter2)
    return this.doSomethingElse(mockedResult)
  }

  private doSomethingElse(mockedResult: string) {
    return mockedResult
  }
}

您将如何进行单元测试UnderTest类? 奖励点是一种在每次测试中或通过输入设置mockedDependency方法结果的方法。

编辑: 对于那些偶然遇到相同问题的人,可能的解决方案:

可以将变量强制转换为所需对象,并像这样重写方法:

const mockedDependency = {
  returnSomething(parameter: string) {
    return parameter    
  }
} as MockedDependency

const underTest = new UnderTest(mockedDependency)

它远非完美,但适用于更简单的情况。

谢谢!

1 个答案:

答案 0 :(得分:0)

这是解决方案:

UnderTest.ts

export interface MockedDependency {
  returnSomething(...args: any[]): any;
  returnSomething2(...args: any[]): any;
}

export default class UnderTest {
  private mockedDependency: MockedDependency;

  constructor(mockedDependency: MockedDependency) {
    this.mockedDependency = mockedDependency;
  }

  public methodUnderTest(parameter: string) {
    const mockedResult = this.mockedDependency.returnSomething(parameter);
    return this.doSomethingElse(mockedResult);
  }

  public methodUnderTest2(parameter1: string, parameter2: string) {
    const mockedResult = this.mockedDependency.returnSomething2(parameter1, parameter2);
    return this.doSomethingElse(mockedResult);
  }

  private doSomethingElse(mockedResult: string) {
    return mockedResult;
  }
}

UnderTest.spec.ts

import UnderTest, { MockedDependency } from './UnderTest';

const mockedDeps: jest.Mocked<MockedDependency> = {
  returnSomething: jest.fn(),
  returnSomething2: jest.fn()
};

const underTest = new UnderTest(mockedDeps);

describe('UnderTest', () => {
  afterEach(() => {
    jest.resetAllMocks();
  });
  describe('#methodUnderTest', () => {
    it('should correctly', () => {
      mockedDeps.returnSomething.mockReturnValueOnce('mocked result');
      const actualValue = underTest.methodUnderTest('1');
      expect(actualValue).toBe('mocked result');
      expect(mockedDeps.returnSomething).toBeCalledWith('1');
    });
  });

  describe('#methodUnderTest2', () => {
    it('should correctly', () => {
      mockedDeps.returnSomething2.mockReturnValueOnce('mocked result');
      const actualValue = underTest.methodUnderTest2('2', '3');
      expect(actualValue).toBe('mocked result');
      expect(mockedDeps.returnSomething2).toBeCalledWith('2', '3');
    });
  });
});

具有100%覆盖率报告的单元测试结果:

 PASS  src/stackoverflow/55966013/UnderTest.spec.ts
  UnderTest
    #methodUnderTest
      ✓ should correctly (5ms)
    #methodUnderTest2
      ✓ should correctly (1ms)

--------------|----------|----------|----------|----------|-------------------|
File          |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
--------------|----------|----------|----------|----------|-------------------|
All files     |      100 |      100 |      100 |      100 |                   |
 UnderTest.ts |      100 |      100 |      100 |      100 |                   |
--------------|----------|----------|----------|----------|-------------------|
Test Suites: 1 passed, 1 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        3.597s, estimated 8s

以下是完整的演示:https://github.com/mrdulin/jest-codelab/tree/master/src/stackoverflow/55966013