在 Jest 中,如何模拟具有依赖项的 TypeScript 类?

Posted

技术标签:

【中文标题】在 Jest 中,如何模拟具有依赖项的 TypeScript 类?【英文标题】:In Jest, how do you mock a TypeScript class with dependencies? 【发布时间】:2020-10-17 13:56:37 【问题描述】:

鉴于这个相对人为的类从 Elasticsearch(或任何数据存储区)获取单个记录:

export default class UserRepoImpl implements UserRepo 
  constructor(private readonly esClient: ElasticsearchClient) 

  public async getUser(id: string): Promise<User> 
    const response = await this.esClient.request('GET', `/users/_doc/$id`);

    return response._source;
  


如何编写一个测试来为我实例化一个UserRepoImpl,但插入一个模拟的ElasticsearchClient作为构造函数的参数?

【问题讨论】:

【参考方案1】:

这里是单元测试解决方案:

userRepoImpl.ts:

interface User 
interface UserRepo 
  getUser(id: string): Promise<User>;

interface ElasticsearchClient 
  request(method: string, endpoint: string): Promise< _source: any >;

export default class UserRepoImpl implements UserRepo 
  constructor(private readonly esClient: ElasticsearchClient) 

  public async getUser(id: string): Promise<User> 
    const response = await this.esClient.request('GET', `/users/_doc/$id`);

    return response._source;
  

userRepoImpl.test.ts:

import UserRepoImpl from './userRepoImpl';

describe('62603645', () => 
  it('should pass', async () => 
    const mUser =  name: 'wen' ;
    const mEsClient =  request: jest.fn().mockResolvedValueOnce( _source: mUser ) ;
    const userRepoImpl = new UserRepoImpl(mEsClient);
    const actual = await userRepoImpl.getUser('1');
    expect(actual).toEqual(mUser);
    expect(mEsClient.request).toBeCalledWith('GET', '/users/_doc/1');
  );
);

带有覆盖率报告的单元测试结果:

 PASS  ***/62603645/userRepoImpl.test.ts (10.988s)
  62603645
    ✓ should pass (5ms)

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

【讨论】:

感谢您非常注重细节的解决方案。你知道你的变量mClientclient之间是否有区别(@teneff提供的解决方案) 我在使用模拟客户端实例化新实例时遇到错误:Argument of type ' request: Mock&lt;any, any&gt;; ' is not assignable to parameter of type 'ElasticsearchClient'.   Type ' request: Mock&lt;any, any&gt;; ' is missing the following properties from type 'ElasticsearchClient': awsHttps, endpoint【参考方案2】:

您可以像这样在测试中创建模拟客户端:

const client: jest.Mocked<ElasticsearchClient> = 
  request: jest.fn()

它会在传递时静态检查解析值的类型:

示例测试:

describe('UserRepoImpl', () => 
  describe('given a client', () => 
    const client: jest.Mocked<ElasticsearchClient> = 
      request: jest.fn()
    
    describe('getUser', () => 
      beforeAll(() => 
        client.request.mockResolvedValue(
          _source: 
            id: '3'
          
        )
      )

      it('should return user by Id', async () => 
        const userRepo = new UserRepoImpl(client)
        await expect(userRepo.getUser("3")).resolves.toEqual(
          id: '3'
        )
      )
    )
  )
)

【讨论】:

我是await expect resolves 本质上是异步的。同步断言原生 promise 解析的内容在物理上是不可能的。一个成功的resolves 断言返回的承诺被忽略,一个失败的承诺不会使测试失败,但会导致未捕获的承诺拒绝。 @EstusFlask 同意。同样返回一个承诺也会有同样的结果 @Teneff 当我模拟客户端时,我收到错误:Type ' request: Mock&lt;any, any&gt;; ' is not assignable to type 'Mocked&lt;ElasticsearchClient&gt;'.   Type ' request: Mock&lt;any, any&gt;; ' is missing the following properties from type 'ElasticsearchClient': awsHttps, endpoint @JeffreyWen 检查您是否使用最新的 jest 和 @types/jest 软件包版本

以上是关于在 Jest 中,如何模拟具有依赖项的 TypeScript 类?的主要内容,如果未能解决你的问题,请参考以下文章

在 Java Play 2.4 中测试具有模拟依赖项的控制器

如何在 Jest 的模拟依赖项中更改方法的返回值?

你们如何测试复杂的反应原生应用程序?

如何为我的 Google 地图应用使用具有 Google Play 服务依赖项的非 Nexus 5 模拟器

如何在 node.js 中卸载具有开发依赖项的 npm 模块?

在 Gradle 中,如何生成具有解析为实际使用版本的动态依赖项的 POM 文件?