配列を仮想DBとした掲示板の作成（4/5）:PUTリクエスト編


# GitHub

https://github.com/atlansien/js_excercise_for_backend_7


# 前提条件
[配列を仮想DBとした掲示板APIサーバの作成（1/5）:はじめに・環境構築・DB、コメントモデルの作成
](https://qiita.com/atlansien/items/43f778e1796a9b7a6ab0)

[配列を仮想DBとした掲示板の作成（2/5）:GETリクエスト・サーバ起動編
](https://qiita.com/atlansien/items/7ee78f6d89c6ae3fbeae)

[配列を仮想DBとした掲示板の作成（3/5）:POSTリクエスト編](https://qiita.com/atlansien/items/9cf58d3b657b8fe72a74)

# この記事のまとめ

- 指定したidとひもづくコメント一件の内容を変更するメソッドを作成した
- PUTリクエストを送った時のメソッドを作成した
- POSTMANでPUTリクエスト時の動きを確認した


# 指定したid値と合致したコメントの情報を更新して、そのコメントを返すメソッドの作成（`updateComment()`）

```js:Comment.js
updateComment: ({ id, username, body }) => {
  if (typeof id !== 'number' || id < 1) {
    throw new Error(
      'idに適切でない値が入っています、1以上の数字を入れてください'
    );
  }
  const comment = comments.find(comment => id === comment.id);
  if (!comment) {
    throw new Error('idと合致するCommentが見つかりません');
  }
  if (!username) {
    throw new Error('usernameは必須です');
  }
  if (!body) {
    throw new Error('bodyは必須です');
  }

  comment.username = username;
  comment.body = body;
  comment.updatedAt = daysjs().format('YYYY年MM月DD日 HH:mm:ss SSS');

  return comment;
},
```

- `username`の引数に値が入っていない
- `body`の引数に値が入っていない
- `id`値に適切でないプロパティ値が入っている
- `id`値と合致するcommentが見つからない

上記の場合はエラーを返し、適切な値が入っている場合のみメソッドが成功するように設計しました。
成功時には、`username`と`body`の他に`updatedAt`も更新したタイミングの日時に変更されるようしました。

それでは実際にうまく動作するかテストしてみようと思います

## `createComment()`のテスト

`test/models`ディレクトリに`updateComment.test.js`ファイルを新たに作成し、そちらでテストを行なっていきます。

### テスト内容

1. `updateComment`はメソッドである
2. `id`の引数に適切でないプロパティ値が入っていた場合、エラーが返される
3. `id`の値と合致する`comment`が見つからない場合、エラーが返される
4. `username`の引数に値が入ってない場合、エラーが返される
5. `body`の引数に値が入ってない場合、エラーが返される
6. 適切なデータが渡された場合、指定した`id`と合致する`comment`の`username`と`body`が変更され、そのデータが返される
7. `6.`と同時に更新された`comment`の`updatedAt`は`createdAt`と時間が違う
8. `6.`と同時に更新された`comment`と、同じID値の配列に組み込まれている`comment`は同じである

この8点を確認するコードを記述していきます

```js:updateComment.test.js
const assert = require('power-assert');
const Comment = require('../../models/Comment');

describe('Comment.updateCommentのテスト', () => {
  // 以下にテストコードを記述していきます
});

```

1. `updateComment`はメソッドである

```js
it('Comment.updateCommentはメソッドである', () => {
  assert.equal(typeof Comment.updateComment, 'function');
});
```
`typeof`演算子で、`updateComment`がメソッドであるかどうかテストします

2 `id`の引数に適切でないプロパティ値が入っていた場合、エラーが返される

```js
it('idの引数に不正な値が入っていた場合エラーが返される', () => {
  const invalidIdList = [
    { id: 0 },
    { id: -1 },
    { id: null },
    { id: {} },
    { id: [] },
    { id: '1' },
  ];

  invalidIdList.forEach(id => {
    try {
      Comment.updateComment(id);
      assert.fail();
    } catch (error) {
      assert.equal(
        error.message,
        'idに適切でない値が入っています、1以上の数字を入れてください'
      );
    }
  });
});
```

`invalidIdList`配列に思いつく限りの適切でない値を入れ、`forEach`で各値ごとに`updateComment`を実行します。エラーになるようであれば`catch`の方でエラーメッセージと比較します。  1つでも成功した場合は`assert.fail()`によりエラーで終了、失敗となります。

3.`id`の値と合致する`comment`が見つからない場合、エラーが返される

```js
it('idのプロパティ値と合致するCommentがない場合エラーが返される', () => {
  const invalidId = { id: 9999999999 };

  try {
    Comment.updateComment(invalidId);
    assert.fail();
  } catch (error) {
    assert.equal(error.message, 'idと合致するCommentが見つかりません');
  }
});
```
`invalidId`に現在は入っていないid値を入れ、`updateComment`を実行します。エラーになるようであれば`catch`の方でエラーメッセージと比較します。成功した場合は`assert.fail()`によりエラーで終了、失敗となります。

4.`username`の引数に値が入ってない場合、エラーが返される

```js
it('usernameの引数に値が入ってない場合エラーが返される', () => {
  const data = { id: 1, body: 'test body' };

  try {
    Comment.updateComment(data);
    assert.fail();
  } catch (error) {
    assert.equal(error.message, 'usernameは必須です');
  }
});
```
 `username`以外のプロパティ値が入っている定数を用意し、`updateComment`を実行します。エラーになるようであれば`catch`の方でエラーメッセージと比較します。成功した場合はエラーで終了、失敗となります。
  
5.`body`の引数に値が入ってない場合、エラーが返される

```js
it('bodyの引数に値が入ってない場合エラーが返される', () => {
  const data = { id: 1, username: 'test user' };

  try {
    Comment.updateComment(data);
    assert.fail();
  } catch (error) {
    assert.equal(error.message, 'bodyは必須です');
  }
});
```
 `body`以外のプロパティ値が入っている定数を用意し、`updateComment`を実行します。エラーになるようであれば`catch`の方でエラーメッセージと比較します。成功した場合はエラーで終了、失敗となります。
 
 6.適切なデータが渡された場合、指定した`id`と合致する`comment`の`username`と`body`が変更され、そのデータが返される
 
 ```js
   it('適切なデータを渡した際、指定したidと合致するcommentのusernameとbodyが変更され、返される', () => {
    const data = { id: 1, username: 'update user', body: 'update body' };

    const changedComment = Comment.updateComment(data);
    assert.deepEqual(changedComment, {
      id: data.id,
      username: data.username,
      body: data.body,
      createdAt: changedComment.createdAt,
      updatedAt: changedComment.updatedAt,
    });


  });
 ```
 
 `id`と合致する`comment`があり、`username`、`body`共に値が入っている場合、`createComment()`は正常に実行され、各プロパティには適切なデータが入っているどうか、`assert.deepEqual()`にてテストします。
 
 
 7.`6.`と同時に更新された`comment`の`updatedAt`は`createdAt`と時間が違う

```js
assert.equal(changedComment.updatedAt > changedComment.createdAt, true); // 変更された日時の方と作成された日時が違うかのテスト
```

`6.`で`updateComment()`実行時、返されたデータの`updatedAt`は更新されているかどうか確認します。更新されているようであれば、`createdAt`より時間は経っているので、`大なり演算子`で`true`が返されるかテストします。

今回こちらでつまづいたのが、テスト実行時、本来なら`true`が返ってくるはずが、結果は`false`でした。

原因は`comment`が作成される時間と更新される時間に差がほとんどなく、現在の表示では、同じ日付になっていました。

```js:修正前の日付の表示設定
dayjs().format("YYYY年MM月DD日 HH:mm:ss");
```

今のままだと、秒数までしか表示されません。なのでさらに細かく*ミリ秒*まで表示することにしました。

```js:修正後の日付の表示設定
dayjs().format("YYYY年MM月DD日 HH:mm:ss SSS");
```

こうすることで、作成日時と更新日時で差が出るようになりました。

8.`6.`と同時に更新された`comment`と、同じID値の配列に組み込まれている`comment`は同じである

```js
const currentComments = Comment.findAll();
assert.deepEqual(currentComments[0], changedComment); // 変更したコメントと同じID値の配列に組み込まれているコメントは同じかテスト
```

6.でupdateComment()実行時、更新されたcommentはきちんと同じID値のcommentと入れ替わっているか確認します。

これらのテストが成功することを確認したら、次はAPIリクエストをした時に、更新されたコメント一件が返ってくるようコーディングします。

# PUTリクエスト時に動作するのメソッドの実装

## `putComment()`

```js:comment.js(controller)
putComment: (req, res) => {
  try {
    const parseId = parseInt(req.params.id, 10);
    const { username, body } = req.body;

    const updatedComment = Comment.updateComment({
      id: parseId,
      username,
      body,
    });

    res.status(200).json(updatedComment);
  } catch (error) {
    res.status(400).json({ message: error.message });
  }
},
```

```js:comment.js(route)
router.route('/:id').put(controller.putComment);
```

今回は、`ルートパラメータ`を使用しアドレスの末端「:id」の部分に数字を入力、そのアドレスを値として受け取ります。

[Express 4.x - API リファレンス:ルート・パラメータ](https://expressjs.com/ja/guide/routing.html#route-parameters)
> ルート・パラメータは、URL内の指定された値を取得するために使用されるURLセグメントのことを言います。捕捉された値はreq.paramsオブジェクトの中で、パスに指定されたルート・パラメータの名前をそれぞれのキーとして設定されます。

ルートパラメータで得たい部分は「`:{任意の名前}`」と書く必要があります。

ルートパラメータで得た値は`req.params`オブジェクトでプロパティ値として使用することができます。

しかし、`req.params`で受け取った値は **文字列(string)** となっているので、`parseInt`で **数値(number)** に変換します。

`username`、`body`は`req.body`で受け取るようにしています。

もし適切なデータを送っていない場合は、エラーメッセージを返します。

## `putComment()`のテスト

`test/app/api`ディレクトリに`putComment.test.js`ファイルを新たに作成し、そちらでテストを行なっていきます。

### テスト内容
1. 適切でない`id`を送ると400エラーが返る
2. 送られた`id`と紐つくコメントがないと400エラーが返る
3. `username`を送らなかった場合400エラーが返る
4. `body`を送らなかった場合400エラーが返る
5. 適切なデータを送った場合、`id`と紐つくコメント一件の`username`と`body`が変更され返ってくる
6. `5.`と同時に、配列内にあった`id`と紐つくコメントは変更されたコメントに上書きされる。

この６点を確認するテストコードを記述していきます

```js:putComment.test.js
const requestHelper = require('../../../helper/requestHelper');
const assert = require('power-assert');

const getComments = async () => {
  const response = await requestHelper.request({
    method: 'get',
    endPoint: '/api/comments',
    statusCode: 200,
  });
  return response.body;
};

const putComment = async (code, id, data) => {
  const response = await requestHelper
    .request({
      method: 'put',
      endPoint: `/api/comments/${id}`,
      statusCode: code,
    })
    .send(data);
  return response;
};

describe('TEST 「PUT api/comments/:id」', () => {
  // 以下にテストコードを記述していきます
});
```

`getComment()`は後々`6.`のテストのさいに使用します。

1. 適切でない`id`を送ると400エラーが返る

```js
it('適切でないidを送ると400エラーが返る', async () => {
  const data = {
    username: 'test user',
    body: 'test body',
  };

  const response = await putComment(400, 0, data);

  assert.deepEqual(response.body, {
    message: 'idに適切でない値が入っています、1以上の数字を入れてください',
  });
});
```
id値に0（適切でない数値）を入れると、ステータスコードは`400`、そしてエラーメッセージが返ってくることを確認します。

2.送られた`id`と紐つくコメントがないと400エラーが返る

```js
it('送られたidと紐つくコメントがないと400エラーが返る', async () => {
  const data = {
    username: 'test user',
    body: 'test body',
  };

  const response = await putComment(400, 9999999, data);

  assert.deepEqual(response.body, {
    message: 'idと合致するCommentが見つかりません',
  });
});
```

現在配列に入っているcommentのどれにも合致しない数字をidに入れると、ステータスコードは`400`、そしてエラーメッセージが返ってくることを確認します。

3.`username`を送らなかった場合400エラーが返る

```js
it('usernameを送らなかった場合400エラーが返る', async () => {
  const data = {
    body: 'test body',
  };

  const response = await putComment(400, 1, data);

  assert.deepEqual(response.body, {
    message: 'usernameは必須です',
  });
});
```
もし`username`のプロパティ値がない場合、ステータスコードは`400`、そしてエラーメッセージが返ってくることを確認します。

4.`body`を送らなかった場合400エラーが返る

```js
it('bodyを送らなかった場合400エラーが返る', async () => {
  const data = {
    username: 'test user',
  };

  const response = await putComment(400, 1, data);

  assert.deepEqual(response.body, {
    message: 'bodyは必須です',
  });
});
```
もし`body`のプロパティ値がない場合、ステータスコードは`400`、そしてエラーメッセージが返ってくることを確認します。

5.適切なデータを送った場合、`id`と紐つくコメント一件の`username`と`body`が変更され返ってくる
6.`5.`と同時に、配列内にあった`id`と紐つくコメントは変更されたコメントに上書きされる。

```js
it('適切なデータを送った場合、idと紐つくコメント一件のusernameとbodyが変更され返ってくる、なお配列内にあったidと紐つくコメントは変更されたコメントに上書きされる', async () => {
  const oldComments = await getComments();

  const data = {
    username: 'test updating user',
    body: 'test updating body',
  };

  const response = await putComment(200, 1, data);

  const comment = response.body;
  assert.deepEqual(comment, {
    id: 1,
    username: data.username,
    body: data.body,
    createdAt: comment.createdAt,
    updatedAt: comment.updatedAt,
  });

  assert.notDeepStrictEqual(oldComments[0], comment);
});
```

今回、変更前のコメントと変更後のコメントが適切に変わっているかどうかの確認で


と`notDeepStrictEqual`を使用していますが、私は最初ここで

```js
assert.equal(oldComments[0] === comment, false);
```
と`assert.equal`を使用しましたが、これはNGです、なぜなら、`oldComments`と`comment`はそれぞれ別の参照から得た値になります。`assert.equal`は参照での比較になりますので、このコードだと、もしお互いのオブジェクトの内容がまったく同じでも`false`が返ってくるため、上記のコードでは不適切なテストになります。また、`false`を返すテストなら`not`系のメソッドを使用するのが適切です。

これらのテストが完了したら、「`「PUT/api/comments/:id」`リクエストを送る、`id値に紐づいたComment1件を更新して、更新したComment1件`がレスポンス値として返ってくる機能の作成」は終了です


## 現在の構成

```:tree
.
├── README.md
├── app.js
├── controllers
│   └── comments.js
├── helper
│   └── requestHelper.js
├── index.js
├── models
│   └── Comment.js
├── package-lock.json
├── package.json
├── routers
│   └── comments.js
└── test
    ├── app
    │   └── api
    │       ├── getComment.test.js
    │       ├── postComment.test.js
    │       └── putComment.test.js
    ├── mocha.opts
    └── models
        ├── createComment.test.js
        ├── findAll.test.js
        └── updateComment.test.js
```

次は「`「DELETE/api/comment/:id」`リクエストを送ると、`id値に紐づいたComment1件を削除して、削除したComment1件`がレスポンス値として返ってくる」機能の実装です。

# POSTMANを使用してPUTリクエスト時の動きをチェックする(gif)

![PUTrequest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349049/bcd12e40-3de9-a5c5-1823-a829e725745d.gif)