GitHub
前提条件
配列を仮想DBとした掲示板APIサーバの作成(1/5):はじめに・環境構築・DB、コメントモデルの作成
配列を仮想DBとした掲示板の作成(2/5):GETリクエスト・サーバ起動編
配列を仮想DBとした掲示板の作成(3/5):POSTリクエスト編
この記事のまとめ
- 指定したidとひもづくコメント一件の内容を変更するメソッドを作成した
- PUTリクエストを送った時のメソッドを作成した
- POSTMANでPUTリクエスト時の動きを確認した
指定したid値と合致したコメントの情報を更新して、そのコメントを返すメソッドの作成(updateComment()
)
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
ファイルを新たに作成し、そちらでテストを行なっていきます。
テスト内容
-
updateComment
はメソッドである -
id
の引数に適切でないプロパティ値が入っていた場合、エラーが返される -
id
の値と合致するcomment
が見つからない場合、エラーが返される -
username
の引数に値が入ってない場合、エラーが返される -
body
の引数に値が入ってない場合、エラーが返される - 適切なデータが渡された場合、指定した
id
と合致するcomment
のusername
とbody
が変更され、そのデータが返される -
6.
と同時に更新されたcomment
のupdatedAt
はcreatedAt
と時間が違う -
6.
と同時に更新されたcomment
と、同じID値の配列に組み込まれているcomment
は同じである
この8点を確認するコードを記述していきます
const assert = require('power-assert');
const Comment = require('../../models/Comment');
describe('Comment.updateCommentのテスト', () => {
// 以下にテストコードを記述していきます
});
-
updateComment
はメソッドである
it('Comment.updateCommentはメソッドである', () => {
assert.equal(typeof Comment.updateComment, 'function');
});
typeof
演算子で、updateComment
がメソッドであるかどうかテストします
2 id
の引数に適切でないプロパティ値が入っていた場合、エラーが返される
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
が見つからない場合、エラーが返される
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
の引数に値が入ってない場合、エラーが返される
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
の引数に値が入ってない場合、エラーが返される
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
が変更され、そのデータが返される
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
と時間が違う
assert.equal(changedComment.updatedAt > changedComment.createdAt, true); // 変更された日時の方と作成された日時が違うかのテスト
6.
でupdateComment()
実行時、返されたデータのupdatedAt
は更新されているかどうか確認します。更新されているようであれば、createdAt
より時間は経っているので、大なり演算子
でtrue
が返されるかテストします。
今回こちらでつまづいたのが、テスト実行時、本来ならtrue
が返ってくるはずが、結果はfalse
でした。
原因はcomment
が作成される時間と更新される時間に差がほとんどなく、現在の表示では、同じ日付になっていました。
dayjs().format("YYYY年MM月DD日 HH:mm:ss");
今のままだと、秒数までしか表示されません。なのでさらに細かくミリ秒まで表示することにしました。
dayjs().format("YYYY年MM月DD日 HH:mm:ss SSS");
こうすることで、作成日時と更新日時で差が出るようになりました。
8.6.
と同時に更新されたcomment
と、同じID値の配列に組み込まれているcomment
は同じである
const currentComments = Comment.findAll();
assert.deepEqual(currentComments[0], changedComment); // 変更したコメントと同じID値の配列に組み込まれているコメントは同じかテスト
6.でupdateComment()実行時、更新されたcommentはきちんと同じID値のcommentと入れ替わっているか確認します。
これらのテストが成功することを確認したら、次はAPIリクエストをした時に、更新されたコメント一件が返ってくるようコーディングします。
PUTリクエスト時に動作するのメソッドの実装
putComment()
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 });
}
},
router.route('/:id').put(controller.putComment);
今回は、ルートパラメータ
を使用しアドレスの末端「:id」の部分に数字を入力、そのアドレスを値として受け取ります。
Express 4.x - API リファレンス:ルート・パラメータ
ルート・パラメータは、URL内の指定された値を取得するために使用されるURLセグメントのことを言います。捕捉された値はreq.paramsオブジェクトの中で、パスに指定されたルート・パラメータの名前をそれぞれのキーとして設定されます。
ルートパラメータで得たい部分は「:{任意の名前}
」と書く必要があります。
ルートパラメータで得た値はreq.params
オブジェクトでプロパティ値として使用することができます。
しかし、req.params
で受け取った値は 文字列(string) となっているので、parseInt
で 数値(number) に変換します。
username
、body
はreq.body
で受け取るようにしています。
もし適切なデータを送っていない場合は、エラーメッセージを返します。
putComment()
のテスト
test/app/api
ディレクトリにputComment.test.js
ファイルを新たに作成し、そちらでテストを行なっていきます。
テスト内容
- 適切でない
id
を送ると400エラーが返る - 送られた
id
と紐つくコメントがないと400エラーが返る -
username
を送らなかった場合400エラーが返る -
body
を送らなかった場合400エラーが返る - 適切なデータを送った場合、
id
と紐つくコメント一件のusername
とbody
が変更され返ってくる -
5.
と同時に、配列内にあったid
と紐つくコメントは変更されたコメントに上書きされる。
この6点を確認するテストコードを記述していきます
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.
のテストのさいに使用します。
- 適切でない
id
を送ると400エラーが返る
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エラーが返る
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エラーが返る
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エラーが返る
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
と紐つくコメントは変更されたコメントに上書きされる。
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
を使用していますが、私は最初ここで
assert.equal(oldComments[0] === comment, false);
とassert.equal
を使用しましたが、これはNGです、なぜなら、oldComments
とcomment
はそれぞれ別の参照から得た値になります。assert.equal
は参照での比較になりますので、このコードだと、もしお互いのオブジェクトの内容がまったく同じでもfalse
が返ってくるため、上記のコードでは不適切なテストになります。また、false
を返すテストならnot
系のメソッドを使用するのが適切です。
これらのテストが完了したら、「「PUT/api/comments/:id」
リクエストを送る、id値に紐づいたComment1件を更新して、更新したComment1件
がレスポンス値として返ってくる機能の作成」は終了です
現在の構成
.
├── 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件
がレスポンス値として返ってくる」機能の実装です。