소비자 테스트 작성
이 자습서는 처음부터 소비자 테스트를 작성하는 방법을 안내합니다. 시작하려면 pact-js를 기반으로 하는 jest-pact를 사용하여 소비자 테스트를 작성합니다. 이 자습서는 MergeRequests#show 페이지에서 호출되는 /discussions.json REST API 엔드포인트에 대한 소비자 테스트를 작성하는 방법을 보여줍니다. 예를 들어 GraphQL 소비자 테스트의 경우 spec/contracts/consumer/specs/project/pipelines/show.spec.js를 참조하십시오.
스켈레톤 생성
소비자 테스트의 스켈레톤을 생성하는 것으로 시작합니다. MergeRequests#show 페이지에서의 요청을위한 파일 show.spec.js를 만들고 다음과 같은 함수 및 매개변수로 채웁니다.
계약 테스트 디렉터리의 구조에 대한 자세한 내용은 테스트 수트 폴더 구조를 참조하십시오.
pactWith 함수
pactWith 함수를 통해 Pact 소비자 테스트가 정의되며 PactOptions 및 PactFn이 들어갑니다.
import { pactWith } from 'jest-pact';
pactWith(PactOptions, PactFn);
PactOptions 매개변수
jest-pact의 PactOptions은 기본 설정을 소개하며, 대부분의 경우 테스트에서 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 메서드를 사용할 수 있습니다. 자세한 내용은 https://jestjs.io/docs/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에 대해 네 가지 속성을 정의하십시오.
- 
state: 요청을 하기 전의 사전 상태에 대한 설명입니다. - 
uponReceiving: 이Interaction이 처리하는 요청 유형에 대한 설명입니다. - 
withRequest: 요청 사양을 정의하는 곳으로 요청method,path, 및headers,body, 또는query가 포함됩니다. - 
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: 'a merge request with discussions exists',
          uponReceiving: 'a request for discussions',
          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('return a successful body', async () => {
      
      });
    });
  },
);
응답 본문 Matchers
예상 응답의 본문에서 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: 'a merge request with discussions exists',
          uponReceiving: 'a request for discussions',
          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('return a successful body', 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라는 파일을 생성하고, 그 안에 request와 response 정의를 넣어주세요.
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: 'a merge request with discussions exists',
    uponReceiving: 'a request for discussions',
  },
  
  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('return a successful body', async () => {
        const discussions = await getDiscussions({
          url: provider.mockService.baseUrl,
        });
        
        expect(discussions).toEqual(Discussions.body);
      });
    });
  },
);
도움말