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.)