Cypress　API呼び出しテスト


〒番号から住所取得できるAPIを呼び出し、Cypressでテストを行ってみた。

〒API：
　https://zipcloud.ibsnet.co.jp/api/search?zipcode=XXXXXXX
　　XXXXXXXはqueryparam

方針：
当初は〒APIをフロントエンドから直接呼出しをしようとしていたが、
フロント側からだとCORSエラーになるため、
今後の拡張考えバックエンドから〒APIを呼ぶようにした。

テストについてはフロントエンドからのコンポーネントテストという形で実施。
※今回モックなし。

ソースは以下においてます。
https://github.com/thithi7110/sample-react-cypress

##### フロント側
```
npm create-react-app sample-react-cypress --template typescript
```

cypressのインストール
```
npm i cypress -D
```

package.jsonにcypress open追加
```package.json
"scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject",
    "cyopen": "cypress open"
  },
```

```
npm run cyopen
```
を実行し、components設定を追加する。

```src/Address.tsx
import React, { useState, useEffect, EventHandler, ChangeEventHandler } from 'react'
import axios from "axios"

type Props = {
    zipcode?: string
    onChange?: (e: string) => void
}
// function AddNum(num1: Number, num2: Number) {
export default function Address(props: Props) {

    const initialState = {
        "zipcode": "",
        "address1": "",
        "address2": "",
        "address3": "",
        "kana1": "",
        "kana2": "",
        "kana3": "",
        "prefcode": "",
    }

    const [zipInfo, setZipInfo] = useState(initialState);

    const getPostInfo = (zipcode: string) => {
        axios.create({
            baseURL: "http://localhost:8085/api",
            headers: {
                "Content-type": "application/json",
            }
        }).get(
            `/zip?zipcode=${zipcode}`
        ).then((res: any) => {
            let inputdata = { ...zipInfo };
            inputdata.address1 = res.data.results[0].address1;
            inputdata.address2 = res.data.results[0].address2;
            inputdata.address3 = res.data.results[0].address3;
            inputdata.kana1 = res.data.results[0].kana1;
            inputdata.kana2 = res.data.results[0].kana2;
            inputdata.kana3 = res.data.results[0].kana3;
            inputdata.prefcode = res.data.results[0].prefcode;
            setZipInfo(inputdata);
        }).catch(e => {
            let error = { ...initialState };
            error.address1 = e;
            setZipInfo(error);

        });
    }

    const onChange = (e: React.ChangeEvent<HTMLInputElement>): void => {

        let inputdata = { ...initialState };
        inputdata.zipcode = e.target.value;
        setZipInfo(inputdata);

        if (e.target.value.length == 7) {
            //API呼び出し
            getPostInfo(e.target.value);
        }
    }

    return (
        <>
            <div>
                <input data-cy='zipcode' value={props.zipcode} onChange={onChange} />
            </div>
            <input data-cy='address1' value={zipInfo.address1} />
            <input data-cy='address2' value={zipInfo.address2} />
            <input data-cy='address3' value={zipInfo.address3} />
            <input data-cy='kana1' value={zipInfo.kana1} />
            <input data-cy='kana2' value={zipInfo.kana2} />
            <input data-cy='kana3' value={zipInfo.kana3} />
            <input data-cy='prefcode' value={zipInfo.prefcode} />
        </>
    );
}


```

テストコード
```cypress/component/Address.cy.tsx
//Adressコンポーネントのテスト
import React, { EventHandler } from 'react'
import Address from '../../src/Address'

describe('住所表示', () => {
    it('Adreesに郵便番号を入力すると郵便番号APIから住所を検索し、郵便番号の下に住所を文字として表示する', () => {
        cy.mount(<Address />)

        cy.get('[data-cy=zipcode]').type('9000025').then(() => {
            cy.get('[data-cy=address1]').should('have.value', "沖縄県");
        })
    })
})

```

##### バック側

```package.json
{
  "name": "nodejs-express-sequelize-postgresql",
  "version": "1.0.0",
  "description": "Node.js Rest Apis with Express",
  "main": "server.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [
    "nodejs",
    "express",
    "rest",
    "api"
  ],
  "author": "kenji",
  "license": "ISC",
  "dependencies": {
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "request": "^2.88.2"
  },
  "devDependencies": {
  }
}
```

パッケージの取得
```
npm i
```

〒APIを呼ぶ箇所
requestで呼び出し
```controller.js
// Find a single Tutorial with an id
exports.findzip = (req, res) => {
  var request = require('request');
  
  const pZipCode = req.query.zipcode;

  // クエリーを準備
  const formData = {
    zipcode: pZipCode
  };

  console.log('pZipCode:'+pZipCode);
  
  try{

    request({url:'http://zipcloud.ibsnet.co.jp/api/search', qs:formData}, (error, response, body) => {
      // 実際の処理
      res.send(response.body);
    });  
  }catch(e){
    res.send({error:"error"});
  }  
};
```

```route.js
module.exports = app => {
  const controller = require("../controllers/controller.js");

  var router = require("express").Router();

  router.get("/zip", controller.findzip);

  app.use("/api", router);
};
```

```service.js
const express = require("express");
// const bodyParser = require("body-parser"); /* deprecated */
const cors = require("cors");

const app = express();
//CORS対策
const whitelist = ['http://localhost:8080', 'http://localhost:51401']
var corsOptions = {
  origin: (origin, callback) => {
    console.log(origin);

    callback(null, true)
  }
};

app.use(cors(corsOptions));

// parse requests of content-type - application/json
app.use(express.json());  /* bodyParser.json() is deprecated */

// parse requests of content-type - application/x-www-form-urlencoded
app.use(express.urlencoded({ extended: true }));   /* bodyParser.urlencoded() is deprecated */

// simple route
app.get("/", (req, res) => {
  res.json({ message: "Welcome to bezkoder application." });
});

require("./app/routes/routes")(app);

// set port, listen for requests
const PORT = process.env.PORT || 8085;
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}.`);
});

```

##### テスト実行
フロント側で以下を実行
```
npm run cyopen
```
コンポーネントテストのAddress.cy.tsxを選択
実行OK
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/653035/462cbc68-acda-52d5-d0a8-f5205298794b.png)

フロント側でAPI呼び出しをinterceptする方法を次回試してみる。