토리맘의 한글라이즈 프로젝트 logo 토리맘의 한글라이즈 프로젝트

스프링 클라우드 컨트랙트 공식 레퍼런스를 한글로 번역한 문서입니다.

전체 목차는 여기에 있습니다.

이번에는 최상위 레벨에 정의하는 요소 중 가장 많이 사용하는 것들을 설명한다:

Description

명세contractdescription을 추가할 수 있다. description은 임의의 텍스트다. 다음 예시를 참고해라:

Groovy YAML Java Kotlin
			org.springframework.cloud.contract.spec.Contract.make {
				description('''
given:
	An input
when:
	Sth happens
then:
	Output
''')
			}
description: Some description
name: some name
priority: 8
ignored: true
request:
  url: /foo
  queryParameters:
    a: b
    b: c
  method: PUT
  headers:
    foo: bar
    fooReq: baz
  body:
    foo: bar
  matchers:
    body:
      - path: $.foo
        type: by_regex
        value: bar
    headers:
      - key: foo
        regex: bar
response:
  status: 200
  headers:
    foo2: bar
    foo3: foo33
    fooRes: baz
  body:
    foo2: bar
    foo3: baz
    nullValue: null
  matchers:
    body:
      - path: $.foo2
        type: by_regex
        value: bar
      - path: $.foo3
        type: by_command
        value: executeMe($it)
      - path: $.nullValue
        type: by_null
        value: null
    headers:
      - key: foo2
        regex: bar
      - key: foo3
        command: andMeToo($it)
Contract.make(c -> {
	c.description("Some description");
}));
contract {
	description = """
given:
	An input
when:
	Sth happens
then:
	Output
"""
}

Name

명세contract의 이름을 제공할 수 있다. should register a user라는 이름을 제공한다고 가정해보자. 이렇게 하면 validate_should_register_a_user라는 이름을 가진 테스트가 자동으로 만들어진다. 또한 WireMock의 스텁stub 이름은 should_register_a_user.json이 된다.

이름 때문에 자동 생성된 테스트 코드가 컴파일에 실패할 수 있으니 주의하자. 여러 명세contract에 같은 이름을 지정했을 때 역시 컴파일에 실패하며, 자동 생성된 스텁stub들이 서로를 덮어쓸 수 있다는 점을 기억해두자.

다음은 명세contract에 이름을 추가하는 방법을 보여주는 예시다:

Groovy YAML Java Kotlin
org.springframework.cloud.contract.spec.Contract.make {
	name("some_special_name")
}
name: some name
Contract.make(c -> {
	c.name("some name");
}));
contract {
	name = "some_special_name"
}

Ignoring Contracts

특정 명세contract를 무시하고 싶다면, 플러그인 설정에 무시할 명세contract를 정의하거나, 컨트랙트 자체에 ignored 프로퍼티를 설정하면 된다. 그 방법은 아래 예시를 참고해라:

Groovy YAML Java Kotlin
org.springframework.cloud.contract.spec.Contract.make {
	ignored()
}
ignored: true
Contract.make(c -> {
	c.ignored();
}));
contract {
	ignored = true
}

Contracts in Progress

명세contract를 아직 개발 중in progress인 것으로 마킹하면 프로듀서producer 측에서 테스트를 생성하지는 않지만 스텁stub은 생성할 수 있다.

실제로 구현이 완료되지 않은 상태에서 컨슈머consumer가 스텁stub을 사용할 수 있기 때문에 주의해서 사용해야 한다.

명세contract를 개발 중in progress으로 마킹하는 방법은 다음을 참고해라:

Groovy YAML Java Kotlin
org.springframework.cloud.contract.spec.Contract.make {
	inProgress()
}
inProgress: true
Contract.make(c -> {
	c.inProgress();
}));
contract {
	inProgress = true
}

Spring Cloud Contract 플러그인의 failOnInProgress 프로퍼티를 설정하면, 소스 코드에 아직 개발 중in progress인 명세contract가 하나라도 남아 있을 시 빌드가 중단되도록 만들 수 있다.

Passing Values from Files

1.2.0 버전부터 파일에 있는 값을 불러와 사용할 수 있다. 프로젝트 내에 다음과 같은 리소스가 있다고 가정해보자:

└── src
    └── test
        └── resources
            └── contracts
                ├── readFromFile.groovy
                ├── request.json
                └── response.json

이어서 명세contract는 다음과 같다고 가정한다:

Groovy YAML Java Kotlin
/*
 * Copyright 2013-2020 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import org.springframework.cloud.contract.spec.Contract

Contract.make {
	request {
		method('PUT')
		headers {
			contentType(applicationJson())
		}
		body(file("request.json"))
		url("/1")
	}
	response {
		status OK()
		body(file("response.json"))
		headers {
			contentType(applicationJson())
		}
	}
}
request:
  method: GET
  url: /foo
  bodyFromFile: request.json
response:
  status: 200
  bodyFromFile: response.json
import java.util.Collection;
import java.util.Collections;
import java.util.function.Supplier;

import org.springframework.cloud.contract.spec.Contract;

class contract_rest_from_file implements Supplier<Collection<Contract>> {

	@Override
	public Collection<Contract> get() {
		return Collections.singletonList(Contract.make(c -> {
			c.request(r -> {
				r.url("/foo");
				r.method(r.GET());
				r.body(r.file("request.json"));
			});
			c.response(r -> {
				r.status(r.OK());
				r.body(r.file("response.json"));
			});
		}));
	}

}
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract

contract {
	request {
		url = url("/1")
		method = PUT
		headers {
			contentType = APPLICATION_JSON
		}
		body = bodyFromFile("request.json")
	}
	response {
		status = OK
		body = bodyFromFile("response.json")
		headers {
			contentType = APPLICATION_JSON
		}
	}
}

JSON 파일은 다음과 같다:

request.json

{
  "status": "REQUEST"
}

response.json

{
  "status": "RESPONSE"
}

테스트나 스텁stub이 만들어질 땐 request.json, response.json 파일의 내용이 요청, 응답 body로 전달된다. 이때 파일명은, 명세contract가 있는 폴더를 기준으로 상대 경로를 작성해야 한다.

파일 내용을 바이너리 형식으로 전달해야 하는 경우, YAML의 bodyFromFileAsBytes 필드를 사용하거나, 그 외 DSL에선 fileAsBytes 메소드를 사용하면 된다.

다음은 바이너리 파일을 사용하는 예시다:

Groovy YAML Java Kotlin
import org.springframework.cloud.contract.spec.Contract

Contract.make {
	request {
		url("/1")
		method(PUT())
		headers {
			contentType(applicationOctetStream())
		}
		body(fileAsBytes("request.pdf"))
	}
	response {
		status 200
		body(fileAsBytes("response.pdf"))
		headers {
			contentType(applicationOctetStream())
		}
	}
}
request:
  url: /1
  method: PUT
  headers:
    Content-Type: application/octet-stream
  bodyFromFileAsBytes: request.pdf
response:
  status: 200
  bodyFromFileAsBytes: response.pdf
  headers:
    Content-Type: application/octet-stream
import java.util.Collection;
import java.util.Collections;
import java.util.function.Supplier;

import org.springframework.cloud.contract.spec.Contract;

class contract_rest_from_pdf implements Supplier<Collection<Contract>> {

	@Override
	public Collection<Contract> get() {
		return Collections.singletonList(Contract.make(c -> {
			c.request(r -> {
				r.url("/1");
				r.method(r.PUT());
				r.body(r.fileAsBytes("request.pdf"));
				r.headers(h -> {
					h.contentType(h.applicationOctetStream());
				});
			});
			c.response(r -> {
				r.status(r.OK());
				r.body(r.fileAsBytes("response.pdf"));
				r.headers(h -> {
					h.contentType(h.applicationOctetStream());
				});
			});
		}));
	}

}
import org.springframework.cloud.contract.spec.ContractDsl.Companion.contract

contract {
	request {
		url = url("/1")
		method = PUT
		headers {
			contentType = APPLICATION_OCTET_STREAM
		}
		body = bodyFromFileAsBytes("contracts/request.pdf")
	}
	response {
		status = OK
		body = bodyFromFileAsBytes("contracts/response.pdf")
		headers {
			contentType = APPLICATION_OCTET_STREAM
		}
	}
}

HTTP 요청을 처리하든 메시지를 처리하든, 둘 모두 바이너리 페이로드가 필요하다면 이 방식을 사용해야 한다.

Metadata

명세contractmetadata를 추가할 수 있다. 메타데이터를 이용하면 추가로 원하는 설정을 전달할 수 있다. 아래에 있는 예시는 wiremock을 키로 사용하고 있다. wiremock 키에 매핑된 값 역시 map인데, 이 map의 키는 stubMapping, 값은 WireMock의 StubMapping 객체다. Spring Cloud Contract는 스텁stub을 매핑할 때, 일부 정보를 커스텀 코드로 수정할 수 있도록 지원한다. 웹훅이나 커스텀 지연을 추가하거나, 써드 파티 WireMock 익스텐션과 통합할 때 유용하다.

groovy yaml java kotlin
Contract.make {
    request {
        method GET()
        url '/drunks'
    }
    response {
        status OK()
        body([
            count: 100
        ])
        headers {
            contentType("application/json")
        }
    }
    metadata([
        wiremock: [
            stubMapping: '''\
                {
                    "response" : {
                        "fixedDelayMilliseconds": 2000
                    }
                }
            '''
            ]
    ])
}
name: "should count all frauds"
request:
  method: GET
  url: /yamlfrauds
response:
  status: 200
  body:
    count: 200
  headers:
    Content-Type: application/json
metadata:
  wiremock:
    stubMapping: >
      {
        "response" : {
          "fixedDelayMilliseconds": 2000
        }
      }
Contract.make(c -> {
    c.metadata(MetadataUtil.map().entry("wiremock", ContractVerifierUtil.map().entry("stubMapping",
            "{ \"response\" : { \"fixedDelayMilliseconds\" : 2000 } }")));
}));
contract {
    metadata("wiremock" to ("stubmapping" to """
{
  "response" : {
    "fixedDelayMilliseconds": 2000
  }
}"""))
}

지원하는 메타데이터 항목은 이어지는 섹션에서 확인할 수 있다.


Next :
3.2. Contracts for HTTP
http 요청, 응답 명세 정의하기

전체 목차는 여기에 있습니다.

<< >>

TOP