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件がレスポンス値として返ってくる」機能の実装です。
