Springboot csvファイル出力 (単体テスト有り)

# 概要
SpringBootでcsvファイルを出力します。
csv出力機能はjackson-dataformats-textというライブラリを使います。
テストはJunit5とMockitoを使います。

# 成果物
ボタンを押すとcsvファイルがダウンロードされます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/1b04ca0b-fc7d-cb9f-2329-3f44171d280c.png)

csvの中身
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/f6f579d8-2029-1080-14c0-b9d1c4b4227b.png)


# 動作環境
OS:Windows10
IDE:IntelliJ Community or CodeSpace
Javaバージョン：11(pom.xmlでバージョン変えればok、17は確認済み)
Spring bootバージョン：2.7.0

# 起動方法
1.githubからプロジェクトをクローンします。branchは「csv_output」を選択します。
https://github.com/RYA234/spring_boot_memo

2.src/main/java/com/example/spring_boot_memo/SpringBootMemoApplicationを実行します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/2cedec91-e6b3-a23e-75e5-7602c1ce38ae.png)

3.以下URLを開きます
http://localhost:5000/
開くと以下場面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2710623/f40ae3aa-ef0e-9b43-1dd9-253a4b20a8ff.png)



# プロダクトコード


## MVC系
### Model(データモデル)
``` CsvData.java
package com.example.spring_boot_memo.csv;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import lombok.Data;
// データモデルです。

@Data
@JsonPropertyOrder({"CODE", "名前", "値段"})
public class CsvData {
    @JsonProperty("CODE")
    int code;
    @JsonProperty("名前")
    String name;
    @JsonProperty("値段")
    int price;

    public CsvData(int code, String name, int price) {
        this.code = code;
        this.name = name;
        this.price = price;
    }
}
```
### Controller
```Controller.java
package com.example.spring_boot_memo.csv;

import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@org.springframework.stereotype.Controller
public class Controller {

    @Autowired
    CsvService csvService;

    @Autowired
    DownloadHelper downloadHelper;

    @RequestMapping(value = "index", params = "OK", method = RequestMethod.GET)
    public String indexController() {
        return "index";
    }

    @RequestMapping(value = "download", method = RequestMethod.POST)
    public ResponseEntity<byte[]> download() throws IOException {
        CsvSchema csvSchema;
        List<CsvData> csvDataList = new ArrayList<>();
        CsvData csvData = new CsvData(1, "寿司", 120);
        csvDataList.add(csvData);
        HttpHeaders headers = new HttpHeaders();
        downloadHelper.addContentDisposition(headers, "日本語ファイル名.csv");
        System.out.print(csvService.getCsvHeader());

        csvSchema = csvService.getCsvHeader();
        return new ResponseEntity<>(csvService.WriteCsvText(csvDataList, csvSchema).getBytes("MS932"), headers, HttpStatus.OK);
    }

}
```
### View
```index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>インデックス画面</title>
</head>
<body>
<form id="csvform" method="post" action="/download">
    <input type="hidden" name="filename"/>
    <button type="submit" name="OK">CSVを取得</button>
</form>

</body>
</html>
```
## Service
```CsvService.java
package com.example.spring_boot_memo.csv;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;

@Service
public class CsvService {
    @Autowired
    private final CsvMapper mapper;

    @Autowired
    private CsvSchema csvSchema;

    //DI
    public CsvService(CsvMapper mapper, CsvSchema csvSchema) {
        this.csvSchema = csvSchema;
        this.mapper = mapper;
    }

    //CsvDataからCsvファイルのヘッダー情報を取得
    public CsvSchema getCsvHeader() {
        // CsvDataの@Jsonpropertyの文字列をヘッダーとして書き込む
        csvSchema = mapper.schemaFor(CsvData.class).withHeader();
        return csvSchema;
    }

    // csvファイルの内容をString型で作成します。
    // 引数のcsvDataListはデータ部分、schemaはヘッダー部分
    public String WriteCsvText(List<CsvData> csvDataList, CsvSchema csvSchema) throws JsonProcessingException {
        return mapper.writer(csvSchema).writeValueAsString(csvDataList);
    }

}


```


## Config(Bean化する用)
jackson-dataformats-textのクラスをBean化します。

```BeanConfig.java
package com.example.spring_boot_memo.csv;

import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class BeanConfig {
    @Bean
    public CsvMapper getCsvMapper() {
        return new CsvMapper();
    }

    @Bean
    public CsvSchema getCsvSchema() {
        return CsvSchema.builder().
                build();
    }
}

```





# テストコード(Serviceのみです)
Serviceの単体テストをします。
``` CsvServiceTest.java
package com.example.spring_boot_memo;

import com.example.spring_boot_memo.csv.CsvData;
import com.example.spring_boot_memo.csv.CsvService;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.dataformat.csv.CsvMapper;
import com.fasterxml.jackson.dataformat.csv.CsvSchema;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.ArrayList;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;

@ExtendWith(MockitoExtension.class)
@SpringBootTest
public class CsvServiceTest {

    @Autowired
    CsvService csvService;

    @Test
    @DisplayName("ヘッダーのみテスト")
    void getCsvHeaderTest() {
        CsvSchema testSchema = csvService.getCsvHeader();
        String expect1 = "CODE";
        String expect2 = "名前";
        String expect3 = "値段";

        String actual1 = testSchema.column(0).getName();
        String actual2 = testSchema.column(1).getName();
        String actual3 = testSchema.column(2).getName();

        assertEquals(expect1, actual1);
        assertEquals(expect2, actual2);
        assertEquals(expect3, actual3);
    }

    @Test
    @DisplayName("Csvファイルで出力される内容確認（ヘッダーと値）")
    void getCsvHeadersTest() throws JsonProcessingException {
        List<CsvData> csvDataList = new ArrayList<>() {
            {
                add(new CsvData(2, "高級焼肉", 1200));
            }
        };
        String actual = csvService.WriteCsvText(csvDataList, csvService.getCsvHeader());
        String expect = "CODE,名前,値段\n2,高級焼肉,1200\n";
        assertEquals(expect, actual);
    }
}

```






# 参考資料
プロダクトコード：

https://qiita.com/t-iguchi/items/75c879fc9a3423c511cb

テストコード：
テストコードは元のgithubに書かれている場合が多いので、それを参考にすると上手く実装できると気づきました。
https://mvnrepository.com/artifact/com.fasterxml.jackson.dataformat/jackson-dataformat-csv/2.13.2