インターフェイス、テスト、モック

昨日までPOSTメソッドとか言ってましたが実際には`rpc CreateTodo`ですね、失礼しました。
実装していきます。

実装していく過程で、テストやモックなどもみていきたいと思います。
では参りましょう。

## インターフェイスの定義

まずインターフェイスを定義し、server構造体からどう呼び出したいかを決めます。
今回はTodoを作成したいので以下のように`server.go`に定義しました。

```go:server.go
...

var _ service.TodoAPIServer = (*server)(nil)

type (
	todo        struct{}
	todoManager interface {
		projectTodo(todo) (string, error)
	}

	server struct {
		todoMgr todoManager
	}
)

func (*server) GetTodo(context.Context, *service.GetTodoRequest) (*service.GetTodoResponse, error) {

...
```

todo構造体を受け取り、保存したらそのidを返します。
サーバーはこの`todoManager`インターフェイスへ依存することにします。

## モックの作成

ではテストを書くために、このインターフェイスからモックを作成しましょう。
モックのコードジェネレートには [github.com/golang/mock/mockgen](https://github.com/golang/mock/tree/master/mockgen) を使います。
今回は直接Makefileにタスクを追加していきましょう。まずはツールインストール用のタスク。

```make
mockgen-install:
	GO111MODULE=off go get github.com/golang/mock/gomock
	go install github.com/golang/mock/mockgen
```

次にコード生成用のタスク。

```make
mockgen:
	mockgen -source=server.go -package=main -destination server_mock.go -mock_names todoManager=MockTodoManager
```

では試してみます。

```sh
$ make mockgen-install
GO111MODULE=off go get github.com/golang/mock/gomock
go install github.com/golang/mock/mockgen

$ make mockgen
mockgen -source=server.go -package=main -destination server_mock.go -mock_names todoManager=MockTodoManager
```

無事`server_mock.go`が生成されているようです。

## テスト

では`server_test.go`を作成し、テストを書いていきましょう。

```go:server_test.go
package main

import "testing"

func TestServer_CreateTodo(t *testing.T) {
}
```

個人的にはTestSuite構造体を作ってテストを書くのがお気に入りです。

```go:server_test.go
type serverTestSuite struct {
	sut     *server
	ctrl    *gomock.Controller
	todoMgr *MockTodoManager
}

func newServerTestSuite(t *testing.T) serverTestSuite {
	ctrl := gomock.NewController(t)
	todoMgr := NewMockTodoManager(ctrl)
	return serverTestSuite{
		sut:     &server{todoMgr: todoMgr},
		ctrl:    ctrl,
		todoMgr: todoMgr,
	}
}
```

まずは保存が成功するパターンをテストしてみましょう。

```go:server_test.go
func TestServer_CreateTodo(t *testing.T) {
	t.Run("project a new todo", func(t *testing.T) {
		s := newServerTestSuite(t)
		defer s.ctrl.Finish()

		input := &service.CreateTodoRequest{}
		want := &service.CreateTodoResponse{}

		got, err := s.sut.CreateTodo(context.Background(), input)
		require.NoError(t, err)
		assert.Equal(t, want, got)
	})
}
```

ここで期待すべきはserver構造体が`todoMgr.projectTodo()`を呼び出すことなので、以下のようにアサーションを追加します。

```go:server_test.go
func TestServer_CreateTodo(t *testing.T) {
	t.Run("project a new todo", func(t *testing.T) {
		s := newServerTestSuite(t)
		defer s.ctrl.Finish()

		input := &service.CreateTodoRequest{}

		todoID := uuid.New().String()
		want := &service.CreateTodoResponse{}

		s.todoMgr.EXPECT().
			projectTodo(todo{}).
			Return(todoID, nil)

		got, err := s.sut.CreateTodo(context.Background(), input)
		require.NoError(t, err)
		assert.Equal(t, want, got)
	})
}
```

inputとwantも値を埋めていきましょう。

```go:server_test.go
func TestServer_CreateTodo(t *testing.T) {
	t.Run("project a new todo", func(t *testing.T) {
		s := newServerTestSuite(t)
		defer s.ctrl.Finish()

		input := &service.CreateTodoRequest{
			Todo: &service.Todo{
				Title:       "foo todo",
				Description: "foo description",
			},
		}

		todoID := uuid.New().String()
		want := &service.CreateTodoResponse{
			Success: true,
			Id:      todoID,
		}

		s.todoMgr.EXPECT().
			projectTodo(todo{}).
			Return(todoID, nil)

		got, err := s.sut.CreateTodo(context.Background(), input)
		require.NoError(t, err)
		assert.Equal(t, want, got)
	})
}
```

最後にtodo構造体を更新してタイトルと説明文が保存されるようにします。

```go:server.go
type (
	todo struct {
		title       string
		description string
	}
	...
)
```

```go:server_test.go
func TestServer_CreateTodo(t *testing.T) {
	t.Run("project a new todo", func(t *testing.T) {
		s := newServerTestSuite(t)
		defer s.ctrl.Finish()

		input := &service.CreateTodoRequest{
			Todo: &service.Todo{
				Title:       "foo todo",
				Description: "foo description",
			},
		}

		todoID := uuid.New().String()
		want := &service.CreateTodoResponse{
			Success: true,
			Id:      todoID,
		}

		s.todoMgr.EXPECT().
			projectTodo(todo{
				title:       input.Todo.Title,
				description: input.Todo.Description,
			}).
			Return(todoID, nil)

		got, err := s.sut.CreateTodo(context.Background(), input)
		require.NoError(t, err)
		assert.Equal(t, want, got)
	})
}
```

いい感じです。テストを実行してみます。

```sh
$ make test
go test -v -cover -timeout 30s ./...
=== RUN   TestServer_CreateTodo
=== RUN   TestServer_CreateTodo/project_a_new_todo
--- FAIL: TestServer_CreateTodo (0.00s)
    --- FAIL: TestServer_CreateTodo/project_a_new_todo (0.00s)
        server_test.go:57: 
                Error Trace:    server_test.go:57
                Error:          Not equal: 
                                expected: &service.CreateTodoResponse{Success: true,
                                Id: "1fbf35f0-9b75-4d28-95fe-ea0dc3d67866",
                                }
                                actual  : &service.CreateTodoResponse{Success: false,
                                Id: "",
                                }
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1,2 +1,2 @@
                                -(*service.CreateTodoResponse)(&CreateTodoResponse{Success:true,Id:1fbf35f0-9b75-4d28-95fe-ea0dc3d67866,})
                                +(*service.CreateTodoResponse)(&CreateTodoResponse{Success:false,Id:,})
                                 
                Test:           TestServer_CreateTodo/project_a_new_todo
        server_test.go:58: missing call(s) to *main.MockTodoManager.projectTodo(is equal to {foo todo foo description}) /Users/kenta/.ghq/github.com/KentaKudo/qiita-advent-calendar-2019/server_test.go:49
        server_test.go:58: aborting test due to missing call(s)
FAIL
coverage: 9.1% of statements
FAIL    github.com/KentaKudo/qiita-advent-calendar-2019 0.682s
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/pb/service     [no test files]
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/schema [no test files]
FAIL
make: *** [test] Error 1
```

いいですね、期待通り失敗しています。

## server.CreateTodoの実装

では、`server.go`を以下のように更新して、もう一度実行してみます。

```go:server.go
func (s *server) CreateTodo(ctx context.Context, req *service.CreateTodoRequest) (*service.CreateTodoResponse, error) {
	id, err := s.todoMgr.projectTodo(todo{
		title:       req.Todo.Title,
		description: req.Todo.Description,
	})
	if err != nil {
		return nil, err
	}

	return &service.CreateTodoResponse{
		Success: true,
		Id:      id,
	}, nil
}
```

```sh
$ make test
go test -v -cover -timeout 30s ./...
=== RUN   TestServer_CreateTodo
=== RUN   TestServer_CreateTodo/project_a_new_todo
--- PASS: TestServer_CreateTodo (0.00s)
    --- PASS: TestServer_CreateTodo/project_a_new_todo (0.00s)
PASS
coverage: 17.4% of statements
ok      github.com/KentaKudo/qiita-advent-calendar-2019 0.695s  coverage: 17.4% of statements
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/pb/service     [no test files]
?       github.com/KentaKudo/qiita-advent-calendar-2019/internal/schema [no test files]
```

無事テストが通りました:)
todoMgrがエラーを返すパターンもテストしますが、目新しいことはないので説明は割愛します。コミットを確認してみてください。

---

残るはstore構造体にtodoManagerインターフェイスを実装する部分ですが、少し長くなってきたのでまた明日にします。では。