0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go】実践的なHTTPハンドラのテストとモック実装

Posted at

はじめまして!フリーランスエンジニアのこたろうです。
HTTPハンドラのテストとモック実装について、学びで得た知見を共有します。

なぜインターフェースを使用するのか

Webアプリケーションのテストにおいて、最も重要な点の1つは「依存関係の分離」です。例えば、記事を取得するAPIのテストを考えてみましょう。このAPIは通常、データベースからデータを取得しますが、テストのたびにデータベースを準備するのは非効率です。

ここで威力を発揮するのが、インターフェースによる抽象化です。以下のように、サービス層をインターフェースとして定義することで、テスト時に実装を自由に差し替えることができます:

// インターフェース定義
type ArticleServicer interface {
    FindAll() ([]Article, error)
    FindByID(id int) (*Article, error)
}

// 本番用の実装
type ArticleService struct {
    db *sql.DB
}

// テスト用のモック実装
type MockArticleService struct {
    articles []Article
}

これにより、以下のような利点が得られます:

  1. データベースに依存しないテストが可能になる
  2. テストのセットアップが簡単になる
  3. テストの実行が高速になる
  4. 様々なエラーケースのテストが容易になる

HTTPハンドラのテスト方法

Goには、HTTPハンドラをテストするための強力なツールが用意されています。特に重要なのがhttptestパッケージです。

テストリクエストの作成

httptest.NewRequest関数を使用すると、実際のHTTP通信なしでリクエストを作成できます:

// GETリクエストの作成
req := httptest.NewRequest("GET", "/articles/1", nil)

// POSTリクエストの作成(JSONボディ付き)
body := strings.NewReader(`{"title": "Test Article"}`)
req := httptest.NewRequest("POST", "/articles", body)

レスポンスの記録と検証

httptest.NewRecorderは、ハンドラが生成したレスポンスを記録するための特別なResponseWriterを提供します:

rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)

// ステータスコードの検証
if rec.Code != http.StatusOK {
    t.Errorf("expected status OK; got %v", rec.Code)
}

// レスポンスボディの検証
var response Article
json.NewDecoder(rec.Body).Decode(&response)

gorilla/muxを使用したルーティングのテスト

gorilla/muxを使用している場合、URLパラメータの取り扱いが重要になります:

func TestArticleHandler_WithMux(t *testing.T) {
    // ルーターの設定
    router := mux.NewRouter()
    mockService := &MockArticleService{
        articles: []Article{{ID: 1, Title: "Test Article"}},
    }
    handler := NewArticleHandler(mockService)
    
    // パスパラメータを使用するルートの登録
    router.HandleFunc("/articles/{id}", handler.GetArticle).Methods("GET")
    
    // テストサーバーの作成
    ts := httptest.NewServer(router)
    defer ts.Close()
    
    // リクエストの実行
    resp, err := http.Get(fmt.Sprintf("%s/articles/1", ts.URL))
    if err != nil {
        t.Fatal(err)
    }
    
    // レスポンスの検証
    if resp.StatusCode != http.StatusOK {
        t.Errorf("expected status OK; got %v", resp.StatusCode)
    }
}

このテストでは、実際のサーバーと同じようにルーティングが機能することを確認できます。gorilla/muxは、URLパラメータを自動的に解析し、ハンドラに渡してくれます。

モックサービスの実装例

テストでは、本番のデータベース操作を模倣したモックサービスを使用します:

type MockArticleService struct {
    articles []Article
    err      error  // テスト用にエラーを注入できる
}

func (m *MockArticleService) FindByID(id int) (*Article, error) {
    if m.err != nil {
        return nil, m.err
    }
    
    for _, article := range m.articles {
        if article.ID == id {
            return &article, nil
        }
    }
    return nil, errors.New("article not found")
}

このモックは:

  • テストケースに応じてレスポンスをカスタマイズ可能
  • エラーケースのテストが容易
  • データベース操作なしで高速にテスト可能

テストの実践的なポイント

  1. テストケースの網羅

    • 正常系(成功)ケース
    • エラーケース(不正なID、データベースエラーなど)
    • バリデーションエラー
    • 空のレスポンスケース
  2. レスポンスの詳細な検証

    • ステータスコード
    • レスポンスヘッダー
    • レスポンスボディの構造と内容
    • エラーメッセージの形式
  3. エッジケースのテスト

    • 不正なJSONリクエスト
    • 存在しないリソース
    • 大きなペイロード
    • 特殊文字を含むパラメータ

参考文献:
-『Goで作るはじめてのWebアプリケーション改訂版』技術評論社

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?