首页 > 解决方案 > 用 Jest 模拟 Flow.js 接口?

问题描述

如何用Jest模拟 Flow.js 接口?令我惊讶的是,我在任何地方都没有发现这个问题。

我对两者都很陌生,但我看到的唯一(未经测试的)选项是创建一个从接口继承的类,然后模拟实现类。这似乎很麻烦,我不相信我可以将实现类(实际上是被模拟的)放在__mocks__Jest 预期的文件夹中并仍然获得预期的行为。

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

更新

为什么要为界面创建模拟?此代码旨在将域层和实现层完全分离,域类对所有注入的依赖项使用 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();
    });
  });
});

如果有人可以提供任何改进,我很乐意!

标签: unit-testingmockingjestjsflowtype

解决方案


问题是,您实际上不需要模拟接口的实现。模拟的目的是“看起来像”真实的东西,但是如果你已经有一个接口来说明真实的东西应该是什么样子,那么任何符合该接口的实现都会自动与模拟一样好。事实上,从类型检查器的角度来看,“真实”和“模拟”实现之间没有区别。

就我个人而言,我喜欢做的是创建一个可以通过提供模拟响应来构建的模拟实现。然后可以在任何测试用例中重用它,方法是直接在该测试用例中构造它并提供它应该提供的确切响应。即,您通过在构建时注入响应来“编写”模拟的脚本。它与您的模拟实现之间的区别在于,如果它没有响应,则会引发异常并导致测试失败。这是我写的一篇文章,展示了这种方法: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();
});

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


推荐阅读