Go言語でデータをCSVファイルとしてマルチパート形式に変換する方法
概要
Go言語でCSVデータの情報を元にDBに更新をかけるAPIを実装をしており、その単体テストの中でmultipart.File
型のテストデータを準備するのにすごく苦労したので記事にしました。
この関数は、[][]string
型のデータを受け取ってマルチパートのCSVファイルに変換し、単体テストなどで利用可能です。
関数の実装
以下の関数 createMultipartDataFromSlice
は、[][]string
型のデータを受け取り、それをマルチパート形式のCSVファイルに変換して返します。
package interactor_test
// import は省略
func TestInteractor_Invoke(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockHogeRepo := mocks.NewMockHogeRepository(ctrl)
interactor := interactor.NewInteractor(
mockHogeRepo,
)
data := [][]string{
{"header1", "header2", "header3"},
{"row1_col1", "row1_col2", "row1_col3"},
{"row2_col1", "row2_col2", "row2_col3"},
}
// 今回紹介するmultipart.Fileを生成する関数
file, err := createMultipartDataFromSlice(data)
if err != nil {
fmt.Printf("Failed to create multipart file: %v\n", err)
return
}
defer file.Close()
content, err := io.ReadAll(file)
if err != nil {
fmt.Printf("Failed to read CSV: %v\n", err)
return
}
fmt.Println(string(content))
ctx := context.Background()
testCases := []struct {
// Field
setupMocks func()
expectedError error
}{
{
// test cases
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
tc.setupMocks()
err := interactor.Invoke(ctx, file)
if tc.expectedError != nil {
assert.Error(t, err)
assert.Equal(t, tc.expectedError, err)
} else {
assert.NoError(t, err)
}
})
}
}
// 指定された`[][]string`型のデータをマルチパートデータに変換
func createMultipartDataFromSlice(data [][]string) (multipart.File, error) {
var buf bytes.Buffer
writer := multipart.NewWriter(&buf)
// フォームファイルとしてデータを追加する
part, err := writer.CreateFormFile("file", "test.csv")
if err != nil {
return nil, fmt.Errorf("error creating form file: %w", err)
}
// CSVライターを作成してデータを書き込む
csvWriter := csv.NewWriter(part)
err = csvWriter.WriteAll(data)
if err != nil {
return nil, fmt.Errorf("error writing data to CSV: %w", err)
}
// ライターを閉じてマルチパートデータを完成させる
csvWriter.Flush()
writer.Close()
// バッファを *os.File に変換する一時ファイルを作成する
tmpFile, err := os.CreateTemp("", "test-*.csv")
if err != nil {
return nil, fmt.Errorf("error creating temp file: %w", err)
}
_, err = tmpFile.Write(buf.Bytes())
if err != nil {
return nil, fmt.Errorf("error writing to temp file: %w", err)
}
// tmpFileを再度読み取れるようにカーソルを先頭に戻す
if _, err := tmpFile.Seek(0, io.SeekStart); err != nil {
return nil, fmt.Errorf("error seeking temp file: %w", err)
}
return tmpFile, nil
}
=== RUN testInteractor_Invoke
--56d604397513fcbd3154df6a39aa9da7bfe68612e54db46788fd4faf5ceb
Content-Disposition: form-data; name="file"; filename="test.csv"
Content-Type: application/octet-stream
header1,header2,header3
row1_col1,row1_col2,row1_col3
row2_col1,row2_col2,row2_col3
まとめ
この関数を使用することで、Goプログラム内でテストしたいcsvデータを[][]stringで準備してあげつ事でマルチパート形式のファイルに変換し、単体テストや他の用途(実装でも使えたりする?)で利用できます。