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

# 配列を仮想DBとした掲示板の作成（3/5）:POSTリクエスト・サーバ起動編

# 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)
# 実装する機能

`「POST/api/comments」`リクエストを送ると、`新規にComment1件を作成し、作成されたComment`がレスポンス値として返ってくる機能の作成

# この記事のまとめ
- 新規にコメントを一件作成し、配列内にpushするメソッドを作成した
- POSTリクエストした時のメソッドを作成した
- POSTMANを用いたPOSTリクエスト時の動作チェックを行なった

# 新規にComment一件を作成し、作成されたCommentを返すメソッドの作成（`createComment()`）

```js:Comment.js
createComment: ({ username, body }) => {
  if (!username) {
    throw new Error('usernameは必須です');
  }
  if (!body) {
    throw new Error('bodyは必須です');
  }

  const comment = new Comment({
    username: username,
    body: body,
  });

  comments.push(comment);

  return comment;
},
```
メソッドの機能として、

- `username`の引数に値が入っていない
- `body`の引数に値が入っていない
 
場合はエラーを投げ、共に適切な値が入っている場合のみメソッドが成功するように設計しました。
それでは実際にうまく動作するかテストしてみようと思います

# `createComment()`のテスト

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

## テスト内容

1. `Comment.createComment`はメソッドである
2. `username`の引数に値が入ってない場合エラーが返される
3. `body`の引数に値が入ってない場合エラーが返される
4. 適切なデータを渡した際、新たにコメントを一件作成し、そのコメント一件は返される
5. `4.`と同時に、`Comment.findAll`で返される配列には`4.`で作成したコメント一件が入っている

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

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

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

```

1 `Comment.createComment`はメソッドである

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

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

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

  dataList.forEach(data => {
    try {
      Comment.createComment(data);
      assert.fail();
    } catch (error) {
      assert.equal(error.message, 'usernameは必須です');
    }
  });
});
```
先に空のオブジェクトと`body`にのみ引数が入っているオブジェクトが格納されている配列を作成し、`forEach`を使いそれらで`createComment()`を実行します。
`createComment()`がうまく実行されず、エラーになった場合、`try/catch`文により例外処理が行われ、`catch`にデータが受け取られます。
`assert.equal()`でエラーメッセージを比較し。適切なエラーメッセージが出た場合、テストは成功です。
もし、`createComment()`が問題なく実行された場合は、次行の`assert.fail()`というエラーを排出するメソッドが実行されるため、テストはエラーで終了、失敗となります。


3 `body`の引数に値が入ってない場合エラーが返される

```js
it('bodyの引数に値が入ってない場合エラーが返される', () => {
  try {
    Comment.createComment({ username: 'test username' });
    assert.fail();
  } catch (error) {
    assert.equal(error.message, 'bodyは必須です');
  }
});
```
流れは`2.`と同じですが、こちらでは`createComment()`に直接オブジェクトを入れています。
これは、`2.`では空オブジェクトを用意しましたが、`3.`で用意した場合、`createComment()`メソッドの流れ上、先に`username`でエラーが出るため、今回は必要ありません。

4 適切なデータを渡した際、新たにコメントを一件作成し、そのコメント一件は返される
5 `4.`と同時に、`Comment.findAll`で返される配列には`4.`で作成したコメント一件が入っている

```js
it('適切なデータを渡した際、新規にコメントを1件作成して、そのコメント一件を返す、配列には新たなコメントが1件入っている', () => {
  const oldComments = Comment.findAll();
  const data = {
    username: 'test username',
    body: 'test body',
  };

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

  const currentComments = Comment.findAll();
  assert.equal(oldComments.length + 1, currentComments.length);
});
```

`username`、`body`共に引数が入っていた場合、`createComment()`は正常に実行され、
各プロパティには適切なデータが入っているどうか、`assert.deepEqual()`にてテストします。

また、それと同時に、作成されたデータは`comments`に格納されているかどうかも確認します。

`createComment()`を実行する前と後の配列を比較することは、現在の`findAll()`ではできません。なぜなら、現在の`findAll()`は**参照渡し**のため、`createComment()`実行前に`findAll()`を宣言しても、上書きされた参照値を返すため比較することができないからです。なので、コードを少し修正する必要があります。

```js:変更前
findAll: () => {
  return comments;
},
```

```js:変更後
findAll: () => {
  return comments.slice();
},
```
`slice`メソッドを使用することにより、宣言時に新たな配列を返すことで、`createComment`実行前と後の配列の差を確認できます。

これらのテストがすべて成功することを確認したら、次はAPIリクエストをした時に、新規作成されたコメント一件が返ってくるメソッドを作成します。

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

## 必要なライブラリのインストール

サーバー側でPOST処理するためのライブラリをインストールします

- [body-parser](https://github.com/expressjs/body-parser)
クライアントからPOST送信されたデータを`req.body`経由で受け取れるようになります。

`use`を用いてミドルウェアから`body-parser`を実行するようにします

```js:app.js
const express = require('express');
const bodyParser = require('body-parser');

const app = express();

app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
```
これで、`body-parser`が実行されるようになりました。
次は、POST送信されたデータを受け取り、コメントを新規作成するためのメソッドを作成します。

#### `postComment()`

```js
postComment: (req, res) => {
  try {
    const { username, body } = req.body;
    const createdComment = Comment.createComment({ username, body });

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

`req.body`経由で受け取ったデータを`username`,`body`に挿入し、それを引数に`createComment()`を実行します。
実行に成功した場合、`createComment()`のデータをjson形式で返します。`username`または`body`のプロパティ値が無く、実行に失敗した場合は、エラーメッセージを返します。

それでは、`postComment()`メソッドが正常に実行するかどうかテストを行います。

## `postComment()`のテスト

今回のテストも、APIの挙動を確認するため`supertest`を使用しますが、記述するコードは`getComments.test.js`で記述したものとほとんど同じです。そして、今後も同じようなテストをする可能性があります。
なので今回は`supertest`の処理を共通化し、自作ライブラリとして使用します。

```js:requestHelper.js
const requestHelper = require('supertest');

const app = require('../app');

module.exports = {
  request: ({ method, endPoint, statusCode }) => {
    return requestHelper(app)
      [method](endPoint)
      .expect('Content-Type', /json/)
      .expect(statusCode);
  },
};
```

値が変わる部分は引数から代入します。

それでは、上記の関数を使って、`postComment()`のテストを行います

## テスト内容

1. `title`を送らなかった場合`400`エラーとエラーメッセージが返る
2. `body`を送らなかった場合`400`エラーとエラーメッセージが返る
3. 適切なデータを送った場合、新規作成されたコメントが一件返される
4. `3.`と同時に、新規作成されたコメント1件が`comments`配列に格納されている

この4点を確認するテストコードを記述します

```js:postComment.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 requestComment = async (code, data) => {
  const response = await requestHelper
    .request({
      method: 'post',
      endPoint: '/api/comments',
      statusCode: code,
    })
    .send(data);
  return response;
};

describe('test 「POST /api/comments」', () => {
  // 以下にテストコードを記述します

});
```
`getComments()`は後々`4.`のテストにて配列の確認をするために使用します。

また、`requestHelper`でライブラリ化したコードも、ここで関数化しておきます。


1 `title`を送らなかった場合`400`エラーとエラーメッセージが返る

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

  const response = await requestComment(400, data);

  assert.deepEqual(response.body, { message: 'usernameは必須です' });
});
```

もし`username`のプロパティ値がない場合、ステータスコードは`400`、そしてエラーメッセージが返ってくることを確認します。

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

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

  const response = await requestComment(400, data);

  assert.deepEqual(response.body, { message: 'bodyは必須です' });
});
```

もし`body`のプロパティ値がない場合、ステータスコードは`400`、そしてエラーメッセージが返ってくることを確認します。


3 適切なデータを送った場合、新規作成されたコメントが一件返される
4 `3.`と同時に、新規作成されたコメント1件が`comments`配列に格納されている

```js
it('適切なデータを送った場合、新規作成されたコメントが一件返ってくる、また配列内には新規作成されたコメントが一件格納されている', async () => {
  const oldComments = await getComments();

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

  const response = await requestComment(200, data);

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

  const currentComments = await getComments();

  assert.equal(oldComments.length + 1, currentComments.length);
});
```
はじめに`getComments()`を実行し、新規作成されたコメントが入る前の配列を`oldComments`に代入します。

その後`requestComment()`を実行し、各プロパティ値のデータは適切かチェックします。
そしてこの時点で配列には新規作成されたコメントが入っているはずなので、その配列を`currentComments`に代入します。
そして、`oldComments`よりも`currentComments`の配列内が１つ多ければ、新規作成されたコメントは配列に格納されていることが判断きます。

これらのテストが完了したら、「`「POST/api/comments」`リクエストを送ると、`新規にComment1件を作成し、作成されたComment`がレスポンス値として返ってくる機能の作成」は終了です。

# 現在の構成

```: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
    ├── mocha.opts
    └── models
        ├── createComment.test.js
        └── findAll.test.js
```

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

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


# おまけ:POSTMANを使用してPOSTリクエスト時の動きをチェックする

`POST`メソッドは、フロント部分を作らないと実際に機能しているかどうかはわかりませんが、今回フロント部分の実装をする予定はありません。

なので今回は**POSTMAN**というツールを使って、起動したサーバーに`POST`メソッドでデータを送信します。

[Postman \| API Development Environment](https://www.getpostman.com/)

使い方
[APIの開発がむちゃくちゃ捗る「Postman」の使い方 - WPJ](https://www.webprofessional.jp/master-api-workflow-postman/)

![POSTrequest.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/349049/fc6a8011-1ccc-8a2b-9e3f-e58a5624dc9b.gif)