📝
Go + Clean Architecture × Testify/mock でユースケース層のテストを書く方法
最近、Go言語で「Clean Architecture」構成のTODOアプリを個人開発しています。
先ほどXでこんなこと呟いていた
完全に主観ですが、個人開発では処理の実装や見た目の実装が最優先になって、
テストコードを書いていない人が多い印象です(実際、自分もそうでした)。
ただ、実務ではテストコードを書くことが前提の開発も多く、
今回の開発では「ユースケース層のテストをちゃんと書いてみよう!」と思い立ち、Testify/mockを使ってテストを導入しました。
GoでClean Architectureを取り入れているけど…
- Usecase層のテスト、どう書けばいいの?
- DBアクセスなしでテストできるようにしたい
- mockの使い方がまだピンとこない
そんな人向けに、Testify/mock を使ったユースケース層のテスト方法を紹介します!
- テストフレームワーク:Testify(
assert
/mock
) - 構成:Usecase層 + RepositoryのInterface + Mock
🎯 検証対象の構成(例:TODO機能)
TodoUsecase
├── CreateTodo(todo *Todo)
├── GetByID(id int)
├── Update(todoID, todo)
├── Delete(todoID, customerID)
└── ChangeStatus(todoID, customerID, completed)
✅ Mockの定義(Repository)
ここでは「この Repository の関数が呼ばれたら、何を返すか」を定義します。
テスト時に本物の DB を使わず、返り値を自分でコントロールできるのがモックの強みです。
type MockTodoRepository struct {
mock.Mock
}
func (m *MockTodoRepository) Create(todo *todo.Todo) error {
args := m.Called(todo)
return args.Error(0)
}
func (m *MockTodoRepository) GetByID(id int) (*todo.Todo, error) {
args := m.Called(id)
return args.Get(0).(*todo.Todo), args.Error(1)
}
func (m *MockTodoRepository) Update(todoID int, todo *todo.Todo) error {
args := m.Called(todoID, todo)
return args.Error(0)
}
...
💡 補足
-
m.Called(...)
→「この関数が呼ばれたよ!」という記録を取ってます -
Return(...)
→「呼ばれたときにこれを返す」という挙動を設定できます
✅ テスト例:CreateTodo
- TODO追加処理の正常系になります。
func TestCreateTodo(t *testing.T) {
repo := new(MockTodoRepository)
usecase := NewTodoUsecase(repo)
repo.On("Create", mock.Anything).Return(nil)
input := &todo.Todo{
Title: "Test Todo",
Description: "Test Description",
TeamID: 1,
CustomerID: 1,
Completed: false,
}
err := usecase.CreateTodo(input)
assert.NoError(t, err)
repo.AssertExpectations(t)
}
💡 補足
-
repo.On("Create", mock.Anything).Return(nil)
→ モックに「Createが呼ばれたらnil
を返す」と設定してます。
mock.Anything
は引数の値が何でもOKという指定です。 -
assert.NoError(t, err)
→ usecaseのCreateTodo
実行時にエラーが発生していないかチェックしています。 -
repo.AssertExpectations(t)
→On(...)
で定義したメソッドが「本当に呼ばれたかどうか」を検証します(=呼び忘れチェック)。
❌ テスト例:権限チェック(異常系)
- TODO編集の異常系です(今回はCustomerIDが違う人からリクエストが来た想定)
- usecaseのUpdateの中でCustomerIDが同じ人かチェックする処理を入れてます。
func TestUpdateUnauthorized(t *testing.T) {
repo := new(MockTodoRepository)
usecase := NewTodoUsecase(repo)
wrongTodo := &todo.Todo{
CustomerID: 2, // ログインユーザーと違う
...
}
input := &todo.Todo{
CustomerID: 1,
...
}
repo.On("GetByID", 1).Return(wrongTodo, nil)
err := usecase.Update(1, input)
assert.Error(t, err)
repo.AssertExpectations(t)
}
💡 補足
-
assert.Error(t, err)
→ 権限エラーなどが期待どおり発生しているかをチェックしています。 -
repo.AssertExpectations(t)
→ モックのGetByID
が正しく呼ばれているか確認します。
✅ まとめ
- Goのユニットテストは
Testify
でだいぶ楽になる - Clean Architectureでは、Usecase層に対するテストが一番実装・確認しやすい
- モックを使うことでDBに依存せず、ビジネスロジックのみを検証できるのが◎
testifyを使ったCRUDユースケースのテスト、慣れると本当に楽です。
同じように悩んでた方の参考になれば嬉しいです!
あと、いろんなエンジニアさんと仲良くなりたいのでXのフォローもぜひ笑
Discussion