【问题标题】:How do you mock an Apollo Server RESTDataSource for unit testing with Jest?您如何模拟 Apollo Server RESTDataSource 以使用 Jest 进行单元测试?
【发布时间】:2020-09-13 06:33:51
【问题描述】:

我正在尝试在我的 Apollo Server 中测试基于 Apollo Server 的 RESTDataSource (https://www.apollographql.com/docs/apollo-server/data/data-sources/#rest-data-source) 的数据源。我正在尝试使用 Jest 对其进行测试。该类具有从外部 REST API 以及调用第二个 API 的另一个模块中提取数据的方法(所以这个 RESTDataSource 最终依赖于两个外部 API,其中一个在这里直接调用,另一个间接调用)。

我不是测试专家,也不清楚如何模拟外部 API。 GraphQL Tools 有一些工具可以让你mock your server,但我不确定那是我想要的。或者我应该使用Jest's methods for mocking ES6 classes,忘记这是一个 GraphQL 服务器?如果是这样,由于我正在使用class,我是否只是使用MyClass.myMethod 之类的方法作为模拟方法来模拟这些方法?

如果我使用 TypeScript(我就是这样),除了将 Jest 设置为与 TypeScript 一起使用之外,我执行此操作的方式有什么变化吗?

显然正确的路线是选择上面的选项之一,但我有点“只见树木不见森林”,也就是说,由于我没有测试经验,我不知道其中哪一个是正确的路线。

感谢任何线索。

【问题讨论】:

  • 提供被测代码。

标签: typescript unit-testing jestjs apollo apollo-server


【解决方案1】:

单元测试

您可以按照apollo-datasource-rest + Typescript + Jest in the Apollo Spectrum chat 中的建议通过模拟apollo-datasource-rest 中的RESTDataSource 来对您的数据源进行单元测试。

对于这个数据源:

import { RESTDataSource } from 'apollo-datasource-rest'

export class MyRestDataSource extends RESTDataSource {
  async getStackoverflow(): Promise<string> {
    return this.get('https://stackoverflow.com/')
  }
}

你可以这样写一个单元测试:

import { MyRestDataSource } from './MyRestDataSource'

const mockGet = jest.fn()
jest.mock('apollo-datasource-rest', () => {
  class MockRESTDataSource {
    baseUrl = ''
    get = mockGet
  }
  return {
    RESTDataSource: MockRESTDataSource,
  }
})

describe('MyRestDataSource', () => {
  it('getStackoverflow gets data from correct URL', async () => {
    const datasource = new MyRestDataSource()

    await datasource.getStackoverflow()

    expect(mockGet).toBeCalledWith('https://stackoverflow.com/')
  })
})

集成测试

比起对数据源进行单元测试,在大多数情况下,我更喜欢使用例如集成测试。 apollo-server-testing:您在服务器上运行 GraphQL 并测试从解析器到数据源的整个路径。如果这样做,请考虑使用例如nock 模拟数据源发出的 HTTP 请求。

打字稿

无论您使用的是 TypeScript 还是 JavaScript,一般方法都应该是相同的,只是有一些细微的差别。例如。使用 JavaScript,您的单元测试可以直接替换数据源中的get

const MyRestDataSource = require('./MyRestDataSource')

describe('MyRestDataSource', () => {
  it('getStackoverflow gets data from correct URL', async () => {
    const datasource = new MyRestDataSource()
    datasource.get = jest.fn()
    await datasource.getStackoverflow()

    expect(datasource.get).toBeCalledWith('https://stackoverflow.com/')
  })
})

但使用 TypeScript 会导致编译器错误,因为 get 受到保护:

MyRestDataSource.test.ts:6:16 - 错误 TS2445:属性“get”受保护,只能在“RESTDataSource”类及其子类中访问。

【讨论】:

    【解决方案2】:

    剧透警告:以下内容与集成测试有关,不在 TypeScript 中,但我认为它可能有助于 OP 或其他希望彻底测试他们的数据源。

    现在给出答案: 你可以从Apollo的优秀完整stack tutorial repo中获得灵感。这对我帮助很大。这是一个示例,您可以在其中看到他们模拟了来自 launchAPIuserAPI 数据源的响应。

        it('books trips', async () => {
        const {server, launchAPI, userAPI} = constructTestServer({
          context: () => ({user: {id: 1, email: 'a@a.a'}}),
        });
    
        // mock the underlying fetches
        launchAPI.get = jest.fn();
    
        // look up the launches from the launch API
        launchAPI.get
          .mockReturnValueOnce([mockLaunchResponse])
          .mockReturnValueOnce([{...mockLaunchResponse, flight_number: 2}]);
    
        // book the trip in the store
        userAPI.store = mockStore;
        userAPI.store.trips.findOrCreate
          .mockReturnValueOnce([{get: () => ({launchId: 1})}])
          .mockReturnValueOnce([{get: () => ({launchId: 2})}]);
    
        // check if user is booked
        userAPI.store.trips.findAll.mockReturnValue([{}]);
    
        const res = await server.executeOperation({
          query: BOOK_TRIPS,
          variables: {launchIds: ['1', '2']},
        });
        expect(res).toMatchSnapshot();
      });
    

    这是他们的constructTestServer 函数。

    const constructTestServer = ({ context = defaultContext } = {}) => {
      const userAPI = new UserAPI({ store });
      const launchAPI = new LaunchAPI();
    
      const server = new ApolloServer({
        typeDefs,
        resolvers,
        dataSources: () => ({ userAPI, launchAPI }),
        context,
      });
    
      return { server, userAPI, launchAPI };
    };
    
    module.exports.constructTestServer = constructTestServer;
    

    这是一个更简单的示例,我在我称为 randomUserApi 的数据源上设置了一个普通的 get 请求。

     it('randomUser > should return expected values', async () => {
        const randomUserApi = new RandomUserApi();
    
        const server = new ApolloServer({
          schema,
          dataSources: () => ({ randomUserApi }),
        });
    
        const mockResponse = {
          results: [
            {
              email: 'jordi.ferrer@example.com',
            },
          ],
          info: {
            seed: '54a56fbcbaf2d311',
          },
        };
    
        randomUserApi.get = jest.fn();
        randomUserApi.get.mockReturnValueOnce(mockResponse);
    
        const query = `query Query {
            randomUser {
              results {
                email
              }
              info {
                seed
              }
            }
          }`;
    
        // run query against the server and snapshot the output
        const response = await server.executeOperation({
          query,
        });
    
        const { data, errors } = response;
    
        expect(errors).toBeUndefined();
        expect(data).toEqual({
          randomUser: {
            info: { seed: '54a56fbcbaf2d311' },
            results: [{ email: 'jordi.ferrer@example.com' }],
          },
        });
      });
    

    这是RandomUserApi的代码:

    const { RESTDataSource } = require('apollo-datasource-rest');
    
    class RandomUserApi extends RESTDataSource {
      constructor() {
        // Always call super()
        super();
        // Sets the base URL for the REST API
        this.baseURL = 'https://randomuser.me/';
      }
    
      async getUser() {
        // Sends a GET request to the specified endpoint
        return this.get('api/');
      }
    }
    
    module.exports = RandomUserApi;
    

    以及使用它的解析器

      Query: {
        randomUser: async (_parent, _args, context) => context.dataSources.randomUserApi.getUser(),
      }
    

    完全披露:发布相同的回复here

    【讨论】:

      猜你喜欢
      • 2021-03-09
      • 2020-05-17
      • 1970-01-01
      • 2021-01-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-11-26
      • 2013-11-18
      相关资源
      最近更新 更多