Golang에서 모의 HTTP 호출
이 블로그 게시물 코드는 go1.16.2에서 실행 중입니다.
테스트할 API 인터페이스
type API interface {
// this function will do http call to external resource
FetchPostByID(ctx context.Context, id int) (*APIPost, error)
}
type APIPost struct {
ID int `json:"id"`
UserID int `json:"userId"`
Title string `json:"title"`
Body string `json:"body"`
}
다음과 같이
API interface FetchPostByID function의 모의 구현을 생성하여 단위 테스트에서 API interface 결과를 간단히 모의할 수 있습니다.API 모의 구현
type APIMock struct {}
func (a APIMock) FetchPostByID(ctx context.Context, id int) (*APIPost, error) {
return nil, fmt.Errorf(http.StatusText(http.StatusNotFound))
}
그러나 이렇게 하면 테스트 범위가 증가하지 않으며 실제 구현
FetchPostByID 내부의 나머지 코드를 건너뜁니다.그래서 우리는
API interface의 테스트 가능한 실제 구현을 먼저 만들 것입니다.구현
HTTP 호출만 모의 처리하려면 http.Client 모의 구현을 만들어야 합니다.
real http.Client에는 HTTP 호출을 원할 때마다 실행되는 Do function가 있습니다. 그래서 우리는 Do function 를 조롱해야 합니다. http.Client에는 구현된 인터페이스가 없으므로 하나 만들어야 합니다.HTTP 클라이언트 모의
type HTTPClient interface {
Do(*http.Request) (*http.Response, error)
}
type HTTPClientMock struct {
// DoFunc will be executed whenever Do function is executed
// so we'll be able to create a custom response
DoFunc func(*http.Request) (*http.Response, error)
}
func (H HTTPClientMock) Do(r *http.Request) (*http.Response, error) {
return H.DoFunc(r)
}
API 구현 구조
func NewAPI(client HTTPClient, baseURL string, timeout time.Duration) API {
return &apiV1{
c: client,
baseURL: baseURL,
timeout: timeout,
}
}
type apiV1 struct {
// we need to put the http.Client here
// so we can mock it inside the unit test
c HTTPClient
baseURL string
timeout time.Duration
}
func (a apiV1) FetchPostByID(ctx context.Context, id int) (*APIPost, error) {
u := fmt.Sprintf("%s/posts/%d", a.baseURL, id)
ctx, cancel := context.WithTimeout(ctx, a.timeout)
defer cancel()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u, nil)
if err != nil {
return nil, err
}
resp, err := a.c.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf(http.StatusText(resp.StatusCode))
}
var result *APIPost
return result, json.NewDecoder(resp.Body).Decode(&result)
}
단위 테스트
var (
// our custom client
client = &HTTPClientMock{}
// our api
api = NewAPI(client, "", 0)
)
func TestApiV1_FetchPostByID(t *testing.T) {
// test table
tt := []struct {
// Body mock the response body
Body string
// StatusCode mock the response statusCode
StatusCode int
// Expected Result
Result *APIPost
// Expected Error
Error error
}{
{
Body: `{"userId": 1,"id": 1,"title": "test title","body": "test body"}`,
StatusCode: 200,
Result: &APIPost{
ID: 1,
UserID: 1,
Title: "test title",
Body: "test body",
},
Error: nil,
},
{
Body: `{"userId": 2,"id": 2,"title": "test title2","body": "test body2"}`,
StatusCode: 200,
Result: &APIPost{
ID: 2,
UserID: 2,
Title: "test title2",
Body: "test body2",
},
Error: nil,
},
{
Body: ``,
StatusCode: http.StatusNotFound,
Result: nil,
Error: fmt.Errorf(http.StatusText(http.StatusNotFound)),
},
{
Body: ``,
StatusCode: http.StatusBadRequest,
Result: nil,
Error: fmt.Errorf(http.StatusText(http.StatusBadRequest)),
},
}
for _, test := range tt {
// we adjust the DoFunc for each test case
client.DoFunc = func(r *http.Request) (*http.Response, error) {
return &http.Response{
// create the custom body
Body: io.NopCloser(strings.NewReader(test.Body)),
// create the custom status code
StatusCode: test.StatusCode,
}, nil
}
// execute the func
p, err := api.FetchPostByID(context.Background(), 0)
// validation
if err != nil && err.Error() != test.Error.Error() {
t.Fatalf("want %v, got %v", test.Error, err)
}
if !reflect.DeepEqual(p, test.Result) {
t.Fatalf("want %v, got %v", test.Result, p)
}
}
}
http.Client만 변경하기 때문에 FetchPostByID func는 다음 줄을 제외하고 있는 그대로 테스트됩니다.resp, err := a.c.Do(req)
a.c.Do는 단위 테스트 내에서 모의DoFunc로 이미 조정되었으므로 a.c.Do 동작은 다음 줄에 따라 변경됩니다.client.DoFunc = func(r *http.Request) (*http.Response, error) {
return &http.Response{
Body: io.NopCloser(strings.NewReader(test.Body)),
StatusCode: test.StatusCode,
}, nil
}
테스트를 실행하자
$ go test ./... -race -coverprofile /tmp/coverage.out && go tool cover -html=/tmp/coverage.out
Reference
이 문제에 관하여(Golang에서 모의 HTTP 호출), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/clavinjune/mocking-http-call-in-golang-15i5텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념
(Collection and Share based on the CC Protocol.)