用Jest模拟Flow.js接口?

时间:2019-05-16 17:39:46

标签: unit-testing mocking jestjs flowtype

如何用Flow.js interface模拟Jest?令我惊讶的是,我还没有找到解决这个问题的地方。

我对两者都相当陌生,但是我看到的唯一(未试用)选项是创建一个从接口继承的类,然后模拟实现类。这似乎很麻烦,而且我不认为我可以将实现类(实际上是要模拟的类)放置在Jest期望的__mocks__文件夹中,但仍然可以获得期望的行为。

有什么建议吗?有没有更合适的模拟工具?

更新

为什么要为接口创建模拟?这段代码旨在将域和实现层完全分开,而域类对所有注入的依赖项都使用Flow接口。我想测试这些域类。理想情况下,使用模拟工具可以使我更轻松,更具表现力地修改模拟服务的行为,并确认正在测试的域类正在对这些模拟服务进行适当的调用。

这是我在这种情况下要测试的类的简化示例。 UpdateResources是要测试的类,而ResourceServerResourceRepository是我要模拟并“监视”的服务的接口:

// @flow

import type { ResourceServer } from '../ResourceServer';
import type { ResourceRepository } from '../ResourceRepository';

/**
 * Use case for updating resources
 */
export default class UpdateResources {
  resourceServer: ResourceServer;
  resourceRepository: ResourceRepository;

  constructor(resourceServer: ResourceServer, resourceRepository: ResourceRepository) {
    this.resourceServer = resourceServer;
    this.resourceRepository = resourceRepository;
  }

  async execute(): Promise<boolean> {
    const updatesAvailable = await this.resourceServer.checkForUpdates();

    if (updatesAvailable) {
      const resources = await this.resourceServer.getResources();
      await this.resourceRepository.saveAll(resources);
    }

    return updatesAvailable;
  }
}

解决方案

我所采用的似乎对我而言效果很好的方法是在__mocks__目录中创建接口的模拟实现,该接口为所有已实现的方法公开jest.fn对象。然后,我使用new实例化这些模拟实现,并跳过对jest.mock()的任何使用。

__ mocks __ / MockResourceServer.js

import type { ResourceServer } from '../ResourceServer';

export default class MockResourceServer implements ResourceServer {

  getResources =  jest.fn(() => Promise.resolve({}));

  checkForUpodates = jest.fn(() => Promise.resolve(true));
}

__ mocks __ / MockResourceRepository.js

import type { ResourceRepository } from '../ResourceRepository';

export default class MockResourceRepository implements ResourceRepository {
  saveAll = jest.fn(() => Promise.resolve());
}

__ tests __ / UpdateResources.test.js

import UpdateResources from '../UpdateResources';
import MockResourceRepository from '../../__mocks__/MockResourceRepository';
import MockResourceServer from '../../__mocks__/MockResourceServer';

describe('UpdateResources', () => {

  describe('execute()', () => {
    const mockResourceServer = new MockResourceServer();
    const mockResourceRepository = new MockResourceRepository();

    beforeEach(() => {
      jest.clearAllMocks();
    });

    it('should check the ResourceServer for updates', async () => {
      const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
      await updateResources.execute();
      expect(mockResourceServer.checkForUpdates).toHaveBeenCalledTimes(1);
    });

    it('should save to ResourceRepository if updates are available', async () => {
      mockResourceServer.load.mockResolvedValue(true);
      const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
      await updateResources.execute();
      expect(mockResourceRepository.saveAll).toHaveBeenCalledTimes(1);
    });

    it('should NOT save to ResourceRepository if NO updates are available', async () => {
      mockResourceServer.load.mockResolvedValue(false);
      const updateResources = new UpdateResources(mockResourceServer, mockResourceRepository);
      await updateResources.execute();
      expect(mockResourceRepository.saveAll).not.toHaveBeenCalled();
    });
  });
});

如果任何人都可以提供任何改进,我就是开放的!

1 个答案:

答案 0 :(得分:0)

问题是,您实际上不需要来模拟接口的实现。模拟的目的是“看起来像”真实的事物,但是如果您已经有一个说明真实事物外观的接口,则符合的任何实现都将自动同样地服务以及模拟。实际上,从类型检查器的角度来看,“真实”和“模拟”实现之间没有什么区别。

我个人想做的是创建一个模拟实现,可以通过提供模拟响应来构造它。然后,可以通过在测试案例中直接构造它并提供确切的响应,来在任何测试案例中重用它。也就是说,您可以通过在构建时注入响应来“编写”模拟内容并写上模拟内容。它与您的模拟实现之间的区别在于,如果它没有响应,它将抛出异常并使测试失败。这是我写的一篇文章,显示了这种方法:https://dev.to/yawaramin/interfaces-for-scaling-and-testing-javascript-1daj

使用这种技术,测试用例可能看起来像这样:

it('should save to ResourceRepository if updates are available', async () => {
  const updateResources = new UpdateResources(
    new MockResourceServer({
      checkForUpdates: [true],
      getResources: [{}],
    }),
    new MockResourceRepository({
      saveAll: [undefined],
    }),
  );

  const result = await updateResources.execute();
  expect(result).toBeTruthy();
});

我喜欢这些模拟游戏,因为所有的响应都是明确的,并向您显示正在发生的调用顺序。