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

5

考虑到下面的这个比较繁琐的类获取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,但在构造函数的参数中插入一个mocked ElasticsearchClient
2个回答

4
你可以在测试中创建一个模拟客户端,就像这样:

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

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 - Estus Flask
1
“resolves” 的本质是异步的。同步断言原生 Promise 的解析结果是不可能的。成功的 “resolves” 断言返回的 Promise 会被忽略,而失败的 Promise 不会导致测试失败,但会导致未捕获的 Promise 拒绝。 - Estus Flask
1
@EstusFlask 同意。同时返回一个 Promise 也会有相同的结果。 - Teneff
2
@Teneff 当我在模拟客户端时,出现了错误:Type '{ request: Mock<any, any>; }' is not assignable to type 'Mocked<ElasticsearchClient>'. Type '{ request: Mock<any, any>; }' is missing the following properties from type 'ElasticsearchClient': awsHttps, endpoint - Jeffrey Wen
@JeffreyWen,请检查您是否正在使用最新版本的jest和@types/jest包。 - Teneff
@Teneff 我的 jest 版本是 26.1.0,@types/jest 是 26.0.3,而 ts-jest 是 26.1.1。 - Jeffrey Wen

2
这里是单元测试解决方案: 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  stackoverflow/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

我很欣赏你非常注重细节的解决方案。你知道你的变量mClientteneff提供的solution`之间有什么区别吗? - Jeffrey Wen
1
当我使用模拟客户端实例化新实例时,出现错误:类型“{ request: Mock<any, any>; }”的参数无法分配给类型“ElasticsearchClient”。类型“{ request: Mock<any, any>; }”缺少类型“ElasticsearchClient”的以下属性:awsHttps、endpoint - Jeffrey Wen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接