소비자 테스트 작성하기

이 튜토리얼은 처음부터 소비자 테스트를 작성하는 방법을 안내합니다. 시작하기 위해, 소비자 테스트는 jest-pact를 사용하여 작성되며, 이는 pact-js 위에서 구축됩니다. 이 튜토리얼에서는 MergeRequests#show 페이지에서 호출되는 /discussions.json REST API 엔드포인트, 즉 /:namespace_name/:project_name/-/merge_requests/:id/discussions.json에 대한 소비자 테스트를 작성하는 방법을 보여줍니다. GraphQL 소비자 테스트의 예는 spec/contracts/consumer/specs/project/pipelines/show.spec.js를 참조하세요.

뼈대 만들기

소비자 테스트의 뼈대를 만드는 것부터 시작하세요. 이는 MergeRequests#show 페이지의 요청을 위한 것이므로, spec/contracts/consumer/specs/project/merge_requests 아래에 show.spec.js라는 파일을 만드세요.

그런 다음, 다음 함수와 매개변수로 이를 채워넣으세요:

계약 테스트 디렉토리 구조에 대한 더 많은 정보는 테스트 스위트 폴더 구조를 참조하세요.

pactWith 함수

Pact 소비자 테스트는 PactOptionsPactFn을 포함하는 pactWith 함수를 통해 정의됩니다.

import { pactWith } from 'jest-pact';

pactWith(PactOptions, PactFn);

PactOptions 매개변수

jest-pactPactOptions추가 옵션을 도입하며, 이는 제공된 pact-js 위에 구축됩니다. 대부분의 경우, 이러한 테스트를 위해 consumer, provider, log, 및 dir 옵션을 정의합니다.

import { pactWith } from 'jest-pact';

pactWith(
  {
    consumer: 'MergeRequests#show',
    provider: 'GET discussions',
    log: '../logs/consumer.log',
    dir: '../contracts/project/merge_requests/show',
  },
  PactFn
);

소비자와 제공자의 이름을 지정하는 방법에 대한 더 많은 정보는 이름 규칙을 참조하세요.

PactFn 매개변수

PactFn은 당신의 테스트가 정의되는 곳입니다. 여기서 모의 제공자를 설정하고 Jest.describe, Jest.beforeEach, 및 Jest.it와 같은 표준 Jest 메서드를 사용할 수 있습니다. 더 많은 정보는 Jest API를 참조하세요.

import { pactWith } from 'jest-pact';

pactWith(
  {
    consumer: 'MergeRequests#show',
    provider: 'GET discussions',
    log: '../logs/consumer.log',
    dir: '../contracts/project/merge_requests/show',
  },

  (provider) => {
    describe('GET discussions', () => {
      beforeEach(() => {

      });

      it('return a successful body', async () => {

      });
    });
  },
);

모의 제공자 설정

테스트를 실행하기 전에, 지정된 요청을 처리하고 지정된 응답을 반환하는 모의 제공자를 설정하십시오. 이를 위해 Interaction에서 상태 및 예상 요청 및 응답을 정의하십시오.

이 튜토리얼에서는 Interaction에 대한 네 가지 속성을 정의하십시오:

  1. state: 요청이 이루어지기 전에 필요한 상태에 대한 설명입니다.
  2. uponReceiving: 이 Interaction이 처리하는 요청의 종류에 대한 설명입니다.
  3. withRequest: 요청 사양을 정의하는 곳입니다. 요청 method, path, 및 headers, body 또는 query를 포함합니다.
  4. willRespondWith: 예상 응답을 정의하는 곳입니다. 응답 status, headers, 및 body를 포함합니다.

Interaction을 정의한 후, addInteraction을 호출하여 해당 상호 작용을 모의 제공자에 추가하십시오.

import { pactWith } from 'jest-pact';
import { Matchers } from '@pact-foundation/pact';

pactWith(
  {
    consumer: 'MergeRequests#show',
    provider: 'GET discussions',
    log: '../logs/consumer.log',
    dir: '../contracts/project/merge_requests/show',
  },

  (provider) => {
    describe('GET discussions', () => {
      beforeEach(() => {
        const interaction = {
          state: '토론이 있는 병합 요청이 존재합니다.',
          uponReceiving: '토론 요청',
          withRequest: {
            method: 'GET',
            path: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
            headers: {
              Accept: '*/*',
            },
          },
          willRespondWith: {
            status: 200,
            headers: {
              'Content-Type': 'application/json; charset=utf-8',
            },
            body: Matchers.eachLike({
              id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
              project_id: Matchers.integer(6954442),
              ...
              resolved: Matchers.boolean(true)
            }),
          },
        };
        provider.addInteraction(interaction);
      });

      it('성공적인 본문을 반환합니다.', async () => {

      });
    });
  },
);

응답 본문 Matchers

응답의 body에서 Matchers를 사용하는 방법에 주목하십시오. 이는 다양한 값을 수용할 만큼 유연하지만 여전히 유효한 값과 유효하지 않은 값을 구별할 수 있을 만큼 엄격합니다. 너무 엄격하거나 너무 느슨하지 않은 단단한 정의가 필요합니다. 다양한 유형의 Matchers에 대해 더 읽어보십시오. 현재 V2 매칭 규칙을 사용하고 있습니다.

테스트 작성

모의 제공자가 설정된 후, 테스트를 작성할 수 있습니다. 이 테스트에서는 요청을 하고 특정 응답을 기대합니다.

먼저, API 요청을 하는 클라이언트를 설정하십시오. 이를 위해 spec/contracts/consumer/resources/api/project/merge_requests.js를 생성하고 다음 API 요청을 추가하십시오. 엔드포인트가 GraphQL인 경우 spec/contracts/consumer/resources/graphql 아래에 생성합니다.

import axios from 'axios';

export async function getDiscussions(endpoint) {
  const { url } = endpoint;

  return axios({
    method: 'GET',
    baseURL: url,
    url: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
    headers: { Accept: '*/*' },
  })
}

설정이 완료되면 이를 테스트 파일에 가져오고 요청을 하기 위해 호출하십시오. 그런 다음, 요청을 하고 기대치를 정의할 수 있습니다.

import { pactWith } from 'jest-pact';
import { Matchers } from '@pact-foundation/pact';

import { getDiscussions } from '../../../resources/api/project/merge_requests';

pactWith(
  {
    consumer: 'MergeRequests#show',
    provider: 'GET discussions',
    log: '../logs/consumer.log',
    dir: '../contracts/project/merge_requests/show',
  },

  (provider) => {
    describe('GET discussions', () => {
      beforeEach(() => {
        const interaction = {
          state: '토론이 있는 병합 요청이 존재합니다.',
          uponReceiving: '토론 요청',
          withRequest: {
            method: 'GET',
            path: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
            headers: {
              Accept: '*/*',
            },
          },
          willRespondWith: {
            status: 200,
            headers: {
              'Content-Type': 'application/json; charset=utf-8',
            },
            body: Matchers.eachLike({
              id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
              project_id: Matchers.integer(6954442),
              ...
              resolved: Matchers.boolean(true)
            }),
          },
        };
      });

      it('성공적인 본문을 반환합니다.', async () => {
        const discussions = await getDiscussions({
          url: provider.mockService.baseUrl,
        });

        expect(discussions).toEqual(Matchers.eachLike({
          id: 'fd73763cbcbf7b29eb8765d969a38f7d735e222a',
          project_id: 6954442,
          ...
          resolved: true
        }));
      });
    });
  },
);

이제 소비자 테스트가 설정되었습니다! 이제 이 테스트를 실행해보십시오.

테스트 가독성 향상

요청 및 응답 정의가 커질 수 있다는 점을 눈치채셨을 것입니다. 이는 테스트를 읽기 어렵게 만들어 원하는 내용을 찾기 위해 많은 스크롤을 해야 합니다. 이러한 내용을 fixture로 분리하여 테스트를 읽기 쉽게 만들 수 있습니다.

spec/contracts/consumer/fixtures/project/merge_requests 경로 아래에 discussions.fixture.js라는 파일을 생성하고, 그곳에 requestresponse 정의를 배치합니다.

import { Matchers } from '@pact-foundation/pact';

const body = Matchers.eachLike({
  id: Matchers.string('fd73763cbcbf7b29eb8765d969a38f7d735e222a'),
  project_id: Matchers.integer(6954442),
  ...
  resolved: Matchers.boolean(true)
});

const Discussions = {
  body: Matchers.extractPayload(body),

  success: {
    status: 200,
    headers: {
      'Content-Type': 'application/json; charset=utf-8',
    },
    body,
  },

  scenario: {
    state: '토론이 있는 병합 요청이 존재합니다',
    uponReceiving: '토론에 대한 요청',
  },

  request: {
    withRequest: {
      method: 'GET',
      path: '/gitlab-org/gitlab-qa/-/merge_requests/1/discussions.json',
      headers: {
        Accept: '*/*',
      },
    },
  },
};

exports.Discussions = Discussions;

모든 내용을 fixture로 이동시킨 후, 다음과 같이 테스트를 간소화할 수 있습니다.

import { pactWith } from 'jest-pact';

import { Discussions } from '../../../fixtures/project/merge_requests/discussions.fixture';
import { getDiscussions } from '../../../resources/api/project/merge_requests';

const CONSUMER_NAME = 'MergeRequests#show';
const PROVIDER_NAME = 'GET discussions';
const CONSUMER_LOG = '../logs/consumer.log';
const CONTRACT_DIR = '../contracts/project/merge_requests/show';

pactWith(
  {
    consumer: CONSUMER_NAME,
    provider: PROVIDER_NAME,
    log: CONSUMER_LOG,
    dir: CONTRACT_DIR,
  },

  (provider) => {
    describe(PROVIDER_NAME, () => {
      beforeEach(() => {
        const interaction = {
          ...Discussions.scenario,
          ...Discussions.request,
          willRespondWith: Discussions.success,
        };
        provider.addInteraction(interaction);
      });

      it('성공적인 응답 본문을 반환합니다', async () => {
        const discussions = await getDiscussions({
          url: provider.mockService.baseUrl,
        });

        expect(discussions).toEqual(Discussions.body);
      });
    });
  },
);