您如何模拟 Apollo Server RESTDataSource 以使用 Jest 进行单元测试?

How do you mock an Apollo Server RESTDataSource for unit testing with Jest?

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

我不是测试专家,我不清楚如何模拟外部 APIs。 GraphQL Tools 有一些工具可以让你 mock your server, but I'm not sure that's what I want. Or should I use Jest's methods for mocking ES6 classes,忘记这是一个 GraphQL 服务器?如果是这样,因为我正在使用 class,我是否只是使用 MyClass.myMethod 之类的东西作为模拟方法来模拟这些方法?

除了设置 Jest 以使用 TypeScript 之外,如果我使用 TypeScript(我就是),我的操作方式有什么变化吗?

显然正确的路线是选择上面的一个选项,但我有点'not seeing the forest for the trees',就是由于我对测试经验不足,我不知道哪个是要遵循的正确路线。

感谢您提供任何线索。

单元测试

您可以按照 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 getWhosebug(): Promise<string> {
    return this.get('https://whosebug.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('getWhosebug gets data from correct URL', async () => {
    const datasource = new MyRestDataSource()

    await datasource.getWhosebug()

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

集成测试

与其对数据源进行单元测试,我在大多数情况下更喜欢集成测试,例如apollo-server-testing: you run GraphQL against the server and test the entire path from the resolver to the data source. If you do so, consider using e.g. nock 模拟数据源发出的 HTTP 请求。

TypeScript

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

const MyRestDataSource = require('./MyRestDataSource')

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

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

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

MyRestDataSource.test.ts:6:16 - error TS2445: Property 'get' is protected and only accessible within class 'RESTDataSource' and its subclasses.

剧透警报:以下内容与集成测试有关,不在 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 的数据源上设置了一个简单的获取请求。

 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(),
  }

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