From 2af6cb4f6a48f08224507f8c77a80045f48ed79d Mon Sep 17 00:00:00 2001 From: Adrian Wannenmacher Date: Sun, 15 Feb 2026 04:27:08 +0100 Subject: [PATCH] test exception types and messages As I noted in commit 85b9c2459c4a53e6e817a29b3b31590a06a57bd1 I noticed that the testing of the invalidity of invalid struct "deserialization" was not sound. That was because if one error was not actually thrown, the test would still be satisfied by the following error. However, if only that specific thing was wrong, no error would be thrown and an invalid struct would be accepted. Therefore I now changed those tests to also check the exception type and its message. This means that if an exception is missing, it is immediately picked up. I also found out that one exception was actually wrong. I had mistyped `TypeError` as `TypError`. Funnily enough that still causes an exception to be thrown at that location, but a `ReferenceError` instead of the `TypeError`. Such bugs should now be more easily noticable. --- data/db.test.js | 18 +++++----- models/game.js | 4 +-- models/game.test.js | 47 +++++++++++++++++-------- models/round.js | 2 +- models/round.test.js | 69 ++++++++++++++++++++++++++----------- models/round_result.js | 2 +- models/round_result.test.js | 27 ++++++++++----- models/session.js | 4 +-- models/session.test.js | 68 ++++++++++++++++++++++++++---------- 9 files changed, 164 insertions(+), 77 deletions(-) diff --git a/data/db.test.js b/data/db.test.js index 9ab1039..72d46a3 100644 --- a/data/db.test.js +++ b/data/db.test.js @@ -61,17 +61,17 @@ export default function() { }); QUnit.test("cannot call constructor", function(assert) { - assert.throws(function() { - new WbDb(); - }); + assert.throws( + function() { new WbDb(); }, + new TypeError("WbDb may not be constructed externally")); - assert.throws(function() { - new WbDb(true); - }); + assert.throws( + function() { new WbDb(true); }, + new TypeError("WbDb may not be constructed externally")); - assert.throws(function() { - new WbDb(true, 1); - }); + assert.throws( + function() { new WbDb(true, 1); }, + new TypeError("WbDb may not be constructed externally")); }); QUnit.test("open db", async function(assert) { diff --git a/models/game.js b/models/game.js index 8d62dba..0e5ef0f 100644 --- a/models/game.js +++ b/models/game.js @@ -179,12 +179,12 @@ export default class Game extends EventTarget { throw new TypeError("struct must contain currentRound as object"); if (this.result.winner === null) { if (value.currentRound === null) - throw new TypeError( + throw new Error( "struct of ongoing game must contain current round"); else this.#currentRound = new Round(value.currentRound); } else if (value.currentRound !== null) - throw new TypeError( + throw new Error( "struct of finished game must not contain current round"); } } diff --git a/models/game.test.js b/models/game.test.js index 43ff67a..74a27cc 100644 --- a/models/game.test.js +++ b/models/game.test.js @@ -24,7 +24,10 @@ export default function() { }); QUnit.test("low goal", function(assert) { - assert.throws(function() { new Game(0); }, "goal must be 1 or higher"); + assert.throws( + function() { new Game(0); }, + new RangeError("goal must be at least 1"), + "goal must be 1 or higher"); }); QUnit.test("higher goal", function(assert) { @@ -265,36 +268,52 @@ export default function() { QUnit.test("fromStruct - invalid", function(assert) { let struct = {}; - function doIt(message) { - assert.throws(function() { new Game(struct); }, message); + function doIt(message, error) { + assert.throws(function() { new Game(struct); }, error, message); } - doIt("no goal"); + doIt("no goal", new TypeError("struct must contain goal as number")); struct.goal = "3"; - doIt("string goal"); + doIt("string goal", new TypeError("struct must contain goal as number")); struct.goal = Math.PI; - doIt("non-int goal"); + doIt( + "non-int goal", + new RangeError("struct must contain goal >= 1 as integer")); struct.goal = 0; - doIt("small goal"); + doIt( + "small goal", + new RangeError("struct must contain goal >= 1 as integer")); struct.goal = 3; - doIt("no rounds"); + doIt("no rounds", new TypeError("struct must contain rounds")); struct.rounds = "nope"; - doIt("rounds not array"); + doIt( + "rounds not array", + new TypeError("struct must contain rounds as array")); struct.rounds = ["nope", "again"]; - doIt("string array rounds"); + doIt( + "string array rounds", + new TypeError("unknown form of RoundResult constructor")); struct.rounds = []; - doIt("no currentRound"); + doIt( + "no currentRound", + new TypeError("struct must contain currentRound as object")); struct.currentRound = "nope"; - doIt("string currentround"); + doIt( + "string currentRound", + new TypeError("struct must contain currentRound as object")); struct.currentRound = null; - doIt("missing currentRound"); + doIt( + "missing currentRound", + new Error("struct of ongoing game must contain current round")); struct.currentRound = new Round().toStruct(); new Game(struct); struct.rounds = [ new RoundResult(3, Team.They).toStruct() ]; - doIt("unneeded currentRound"); + doIt( + "unneeded currentRound", + new Error("struct of finished game must not contain current round")); struct.currentRound = null; new Game(struct); }); diff --git a/models/round.js b/models/round.js index c3d3570..b52e324 100644 --- a/models/round.js +++ b/models/round.js @@ -56,7 +56,7 @@ export class Round extends EventTarget { } else if (typeof value === "object" && theyLimit === undefined) { this.#fromStruct(value); } else { - throw new TypeError("unknown form for Round constructor"); + throw new TypeError("unknown form of Round constructor"); } } diff --git a/models/round.test.js b/models/round.test.js index 53baabc..df791c2 100644 --- a/models/round.test.js +++ b/models/round.test.js @@ -26,9 +26,10 @@ export default function() { QUnit.test("multiple victories", function(assert) { let round = new Round(); round.winner = Team.They; - assert.throws(function() { - round.winner = Team.We; - }, "victory cannot be stolen"); + assert.throws( + function() { round.winner = Team.We; }, + new Error("decided round cannot be won again"), + "victory cannot be stolen"); }); QUnit.test("single raise", function(assert) { @@ -155,45 +156,71 @@ export default function() { QUnit.test("fromStruct - invalid", function(assert) { let struct = {}; - function doIt(message) { - assert.throws(function() { new Round(struct); }, message); + function doIt(message, error) { + assert.throws(function() { new Round(struct); }, error, message); } - doIt("no points"); + doIt("no points", new TypeError("struct must contain points as number")); struct.points = "2"; - doIt("string points"); + doIt( + "string points", + new TypeError("struct must contain points as number")); struct.points = 1.5; - doIt("non-int points"); + doIt( + "non-int points", + new RangeError("struct must contain points >= 2 as integer")); struct.points = 1; - doIt("small points"); + doIt( + "small points", + new RangeError("struct must contain points >= 2 as integer")); struct.points = 2; - doIt("no raisedLast"); + doIt("no raisedLast", new TypeError("struct must contain raisedLast")); struct.raisedLast = "we"; - doIt("string raisedLast"); + doIt( + "string raisedLast", + new TypeError("struct must contain raisedLast as Team or null")); struct.raisedLast = -1; - doIt("raisedLast not actual team"); + doIt( + "raisedLast not actual team", + new TypeError("struct must contain raisedLast as Team or null")); struct.raisedLast = null; - doIt("no winner"); + doIt("no winner", new TypeError("struct must contain winner")); struct.winner = "they"; - doIt("string winner"); + doIt( + "string winner", + new TypeError("struct must contain winner as Team or null")); struct.winner = -1; - doIt("winner not actual team"); + doIt( + "winner not actual team", + new TypeError("struct must contain winner as Team or null")); struct.winner = null; - doIt("no ourLimit"); + doIt( + "no ourLimit", + new TypeError("struct must contain ourLimit as number")); struct.ourLimit = "11"; - doIt("string ourLimit"); + doIt( + "string ourLimit", + new TypeError("struct must contain ourLimit as number")); struct.ourLimit = 1; - doIt("small ourLimit"); + doIt( + "small ourLimit", + new RangeError("struct must contain ourLimit >= 2 as integer")); struct.ourLimit = 11; - doIt("no theirLimit"); + doIt( + "no theirLimit", + new TypeError("struct must contain theirLimit as number")); struct.theirLimit = "11"; - doIt("string theirLimit"); + doIt( + "string theirLimit", + new TypeError("struct must contain theirLimit as number")); struct.theirLimit = 1; - doIt("small theirLimit"); + doIt( + "small theirLimit", + new RangeError("struct must contain theirLimit >= 2 as integer")); struct.theirLimit = 11; new Round(struct); diff --git a/models/round_result.js b/models/round_result.js index c27b280..8032e12 100644 --- a/models/round_result.js +++ b/models/round_result.js @@ -21,7 +21,7 @@ export default class RoundResult { } else if (typeof value === "object" && winner === undefined) { this.#fromStruct(value); } else { - throw new TypeError("unknown form for RoundResult constructor"); + throw new TypeError("unknown form of RoundResult constructor"); } } diff --git a/models/round_result.test.js b/models/round_result.test.js index 6e304ba..584fcc6 100644 --- a/models/round_result.test.js +++ b/models/round_result.test.js @@ -32,24 +32,33 @@ export default function() { QUnit.test("fromStruct - invalid", function(assert) { let struct = {}; - function doIt(message) { - assert.throws(function() { new Round(struct); }, message); + function doIt(message, error) { + assert.throws(function() { new RoundResult(struct); }, error, message); } - doIt("no points"); + doIt("no points", new TypeError("struct must contain points as number")); struct.points = "4"; - doIt("string points"); + doIt( + "string points", + new TypeError("struct must contain points as number")); struct.points = 4.1; - doIt("non-int points"); + doIt( + "non-int points", + new RangeError("struct must contain points >= 2 as integer")); struct.points = 1; - doIt("small points"); + doIt( + "small points", + new RangeError("struct must contain points >= 2 as integer")); struct.points = 4; - doIt("no winner"); + doIt("no winner", new TypeError("struct must contain winner")); struct.winner = "they"; - doIt("string winner"); + doIt( + "string winner", new TypeError("struct must contain winner as Team")); struct.winner = -1; - doIt("non-team winner"); + doIt( + "non-team winner", + new TypeError("struct must contain winner as Team")); struct.winner = Team.They; new RoundResult(struct); diff --git a/models/session.js b/models/session.js index b039926..7256191 100644 --- a/models/session.js +++ b/models/session.js @@ -145,7 +145,7 @@ export default class Session { } if (typeof value.goal !== "number") - throw new TypError("struct must contain goal as number"); + throw new TypeError("struct must contain goal as number"); if (!Number.isInteger(value.goal) || value.goal < 1) throw new RangeError("struct must contain goal >= 1 as integer"); this.#goal = value.goal; @@ -172,7 +172,7 @@ export default class Session { if (value.currentGame !== null) { this.#currentGame = new Game(value.currentGame); if (this.#currentGame.result.winner !== null) - throw new Error("currentGame in struct mustnot be finished"); + throw new Error("currentGame in struct must not be finished"); } } } diff --git a/models/session.test.js b/models/session.test.js index 94478b1..3f4be85 100644 --- a/models/session.test.js +++ b/models/session.test.js @@ -24,7 +24,18 @@ export default function() { assert.strictEqual(session.goal, 11, "initial goal"); session.goal = 3; assert.strictEqual(session.goal, 3, "changed goal"); - assert.throws(function() { session.goal = 0; }, "invalid goal"); + assert.throws( + function() { session.goal = "0"; }, + new TypeError("goal must be a number"), + "string goal"); + assert.throws( + function() { session.goal = 0.5; }, + new RangeError("goal must be integer >= 1"), + "float goal"); + assert.throws( + function() { session.goal = 0; }, + new RangeError("goal must be integer >= 1"), + "small goal"); }); QUnit.test("start game", function(assert) { @@ -187,8 +198,8 @@ export default function() { QUnit.test("fromStruct - invalid", function(assert) { let struct = {}; - function doIt(message) { - assert.throws(function() { new Session(struct); }, message); + function doIt(message, error) { + assert.throws(function() { new Session(struct); }, error, message); } let unfinished = new Game(3); @@ -197,39 +208,60 @@ export default function() { finished.currentRound.raise(Team.We); finished.currentRound.winner = Team.They; - doIt("no goal"); + doIt("no goal", new TypeError("struct must contain goal as number")); struct.goal = "3"; - doIt("string goal"); + doIt("string goal", new TypeError("struct must contain goal as number")); struct.goal = Math.PI; - doIt("non-int goal"); + doIt( + "non-int goal", + new RangeError("struct must contain goal >= 1 as integer")); struct.goal = 0; - doIt("small goal"); + doIt( + "small goal", + new RangeError("struct must contain goal >= 1 as integer")); struct.goal = 3; - doIt("no ourTeam"); + doIt( + "no ourTeam", new TypeError("struct must contain ourTeam as string")); struct.ourTeam = 5; - doIt("number ourTeam"); + doIt( + "number ourTeam", + new TypeError("struct must contain ourTeam as string")); struct.ourTeam = ""; - doIt("no theirTeam"); + doIt( + "no theirTeam", + new TypeError("struct must contain theirTeam as string")); struct.theirTeam = 6; - doIt("number theirTeam"); + doIt( + "number theirTeam", + new TypeError("struct must contain theirTeam as string")); struct.theirTeam = ""; - doIt("no games"); + doIt("no games", new TypeError("struct must contain games")); struct.games = "nope"; - doIt("string games"); + doIt( + "string games", new TypeError("struct must contain games as array")); struct.games = ["nope", "again"]; - doIt("string array games"); + doIt( + "string array games", + new TypeError("unknown form of Game constructor")); struct.games = [unfinished.toStruct()]; - doIt("unfinished game in games"); + doIt( + "unfinished game in games", new Error("past games must be finished")); struct.games = [finished.toStruct()]; - doIt("no currentGame"); + doIt( + "no currentGame", + new TypeError("struct must contain currentGame as object")); struct.currentGame = "nope"; - doIt("string currentGame"); + doIt( + "string currentGame", + new TypeError("struct must contain currentGame as object")); struct.currentGame = finished.toStruct(); - doIt("finished currentGame"); + doIt( + "finished currentGame", + new Error("currentGame in struct must not be finished")); struct.currentGame = unfinished.toStruct(); new Session(struct);