소비자 테스트 작성
이 자습서는 처음부터 소비자 테스트를 작성하는 방법을 안내합니다. 먼저, 소비자 테스트는 pact-js
를 기반으로 하는 jest-pact
를 사용하여 작성됩니다. 이 자습서에서는 MergeRequests#show
페이지에서 호출되는 /discussions.json
REST API 엔드포인트에 대한 소비자 테스트를 작성하는 방법을 보여줍니다. GraphQL 소비자 테스트의 예는 spec/contracts/consumer/specs/project/pipelines/show.spec.js
를 참조하십시오.
스켈레톤 생성
먼저 소비자 테스트의 스켈레톤을 생성합니다. 이것은 MergeRequests#show
페이지의 요청을 위한 것이므로, spec/contracts/consumer/specs/project/merge_requests
디렉터리 아래에 show.spec.js
라는 파일을 생성합니다. 그런 다음, 다음 함수와 매개변수로 채웁니다:
계약 테스트 디렉터리의 구조에 대한 자세한 정보는 테스트 스위트 폴더 구조를 참조하십시오.
pactWith
함수
Pact 소비자 테스트는 PactOptions
와 PactFn
을 사용하는 pactWith
함수를 통해 정의됩니다.
import { pactWith } from 'jest-pact';
pactWith(PactOptions, PactFn);
PactOptions
매개변수
jest-pact
의 PactOptions
는 추가 옵션을 소개하여 기존의 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 메서드를 사용할 수 있는 곳입니다. 자세한 정보는 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 () => {
});
});
},
);
응답 body 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하고 요청을 만드는 호출하면 됩니다. 요청을 만들고 기대하는 바를 정의할 수 있습니다.
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);
});
});
},
);