unit-testing - 用 Jest 模拟 Flow.js 接口?
问题描述
如何用Jest模拟 Flow.js 接口?令我惊讶的是,我在任何地方都没有发现这个问题。
我对两者都很陌生,但我看到的唯一(未经测试的)选项是创建一个从接口继承的类,然后模拟实现类。这似乎很麻烦,我不相信我可以将实现类(实际上是被模拟的)放在__mocks__
Jest 预期的文件夹中并仍然获得预期的行为。
有什么建议么?有更合适的模拟工具吗?
更新
为什么要为界面创建模拟?此代码旨在将域层和实现层完全分离,域类对所有注入的依赖项使用 Flow 接口。我想测试这些域类。理想情况下,使用模拟工具可以让我更轻松、更有表现力地修改模拟服务的行为,并确认正在测试的域类正在对这些模拟服务进行适当的调用。
这是我将在这种情况下测试的类的简化示例。 UpdateResources
将是正在测试的类,而ResourceServer
和ResourceRepository
是我想模拟和“监视”的服务的接口:
// @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();
});
});
});
如果有人可以提供任何改进,我很乐意!
解决方案
问题是,您实际上不需要模拟接口的实现。模拟的目的是“看起来像”真实的东西,但是如果你已经有一个接口来说明真实的东西应该是什么样子,那么任何符合该接口的实现都会自动与模拟一样好。事实上,从类型检查器的角度来看,“真实”和“模拟”实现之间没有区别。
就我个人而言,我喜欢做的是创建一个可以通过提供模拟响应来构建的模拟实现。然后可以在任何测试用例中重用它,方法是直接在该测试用例中构造它并提供它应该提供的确切响应。即,您通过在构建时注入响应来“编写”模拟的脚本。它与您的模拟实现之间的区别在于,如果它没有响应,则会引发异常并导致测试失败。这是我写的一篇文章,展示了这种方法: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();
});
我喜欢这些模拟的地方是所有响应都是明确的,并向您显示正在发生的调用顺序。
推荐阅读
- node.js - 如何在 Node.js 应用程序中自动重新加载更新的 SSL 证书
- python - mysql中持续时间的唯一值
- java - Android - 如何在不使用 Intent 的情况下发送 ArrayList?
- java - Hibenate“参数值与预期类型不匹配”
- python-3.x - 我该如何解决这个错误?你应该注意什么?
- python - Waf 配置错误,由于某种原因找不到 pyembed
- database - 猫鼬 $literal 在查找方法投影中
- r - 根据条件创建新列
- reactjs - React 在 Redux 中存储 Ref 元素
- mysql - 如果 MySQL INSERT 查询中不存在,则自动生成主键