Writing Test Cases - JavaScript

  1. Which test rules out the suspected implementation and also verifies the missing-ID result?
    Answer: assert.strictEqual(findUser(users, 2), users[1]); assert.strictEqual(findUser(users, 9), null); When testing a function for correctness, your goal isn't just to verify that it works — it's to verify that it works for the right reason. The suspected bug here is subtle: the broken implementation always returns the first element (users[0]) whenever the target ID exists anywhere in the array. To catch this, you need a test that asks for an element that is not at index 0 and checks the exact object returned. That's exactly what B does. assert.strictEqual(findUser(users, 2), users[1]) requests ID 2, which belongs to users[1] (Bo). A buggy implementation would return users[0] (Ana) instead, causing this assertion to fail — exposing the bug. The second line, assert.strictEqual(findUser(users, 9), null), confirms the missing-ID behavior. Together, these two assertions are both necessary and sufficient. A fails to catch the bug because assert.ok(findUser(users, 2)) only checks that the return value is truthy — the broken implementation returning users[0] is still truthy, so it passes undetected. C seems reasonable but actually cannot catch the bug. It requests ID 1, which belongs to users[0]. The broken implementation also returns users[0] for any existing ID, so this test passes even when the function is wrong. D has the same weakness as A — assert.notStrictEqual(..., null) only confirms the result isn't null, not that it's the correct object. Study tip: When writing unit tests, always check the specific value returned, not just its existence or truthiness — use strictEqual with the exact expected reference or value to expose subtle logic errors.
  2. Which test correctly and completely checks the no-match behavior?
    Answer: const items = ["a", "b"]; const result = removeFirst(items, "x"); assert.strictEqual(result, false); assert.deepStrictEqual(items, ["a", "b"]); When testing a function's "no-match" behavior, you need to verify two independent contracts: the return value and the side effect (or lack thereof). Missing either one leaves your test incomplete. For removeFirst(items, "x") where "x" doesn't exist in ["a", "b"], the spec demands the function returns false and leaves the array unchanged. Option C checks both of these correctly — result is strictly equal to false, and items still deep-equals ["a", "b"]. This is the complete, accurate test. Option A fails on the return value check: it asserts result is true, but the spec explicitly says a no-match must return false. Even if the array check were correct, the assertion would fail on a properly implemented function. Option B gets the return value right (false) but then asserts items equals ["a"] — meaning it expects the array to be mutated even on a no-match. This directly contradicts the requirement to leave the array unchanged. It's testing for buggy behavior. Option D also gets false correct, but uses assert.notDeepStrictEqual(items, ["a", "b"]) — which asserts the array was changed. That's the opposite of what you want. A correct implementation would actually fail this test, making it worse than useless. Study tip: When writing tests for functions with both a return value and a side effect, always treat them as two separate assertions. Ask yourself: "What should come back?" and "What should the state look like afterward?" — then assert both explicitly.
  3. Which assertion correctly tests the required rejection?
    Answer: await assert.rejects(loadUser(0), TypeError); When testing promise rejections in Node.js, the key distinction is between synchronous throws and asynchronous rejections — and using the right assertion tool for each. assert.rejects() is designed specifically for promises. It accepts either a promise or an async function, waits for that promise to settle, and then checks whether it rejected with the expected error type. Because it returns a promise itself, you must await it to actually catch and evaluate the result. That makes D the correct choice: await assert.rejects(loadUser(0), TypeError) properly waits for loadUser(0)'s promise to reject and confirms the rejection reason is a TypeError. Here's why the other options fall short. A uses assert.rejects correctly in structure, but passes RangeError instead of TypeError — the specification explicitly requires a TypeError, so this would fail the test even though the pattern is otherwise right. B uses assert.throws, which is built for synchronous exceptions. Calling loadUser(0) inside the callback doesn't throw synchronously — it returns a promise — so assert.throws will see no thrown error and incorrectly pass (or behave unpredictably), never actually checking the rejection. C combines the wrong tool (assert.throws) with an async callback. While async functions can be passed to assert.throws, it doesn't understand how to await them, so the rejection goes unhandled and unverified. A useful rule of thumb: if the error travels through a promise rejection, always reach for assert.rejects and remember to await it. Reserve assert.throws strictly for code that throws synchronously.
  4. Which test most accurately verifies the upper valid boundary and the first invalid value above it?
    Answer: assert.strictEqual(parsePort("65535"), 65535); assert.throws(() => parsePort("65536"), RangeError); When testing boundary conditions, you need two things working together: confirming the last valid value succeeds and confirming the first invalid value throws the right error type. This question tests whether you can recognize both the correct assertion logic and the correct assert.throws syntax. Answer A gets both right. assert.strictEqual(parsePort("65535"), 65535) confirms the upper boundary returns the expected numeric value, and assert.throws(() => parsePort("65536"), RangeError) confirms that crossing the boundary throws specifically a RangeError. Notice that parsePort("65536") is wrapped in an arrow function — this is critical because assert.throws needs a function to call, not an already-evaluated expression. Answer B has the logic completely reversed: it expects 65535 to throw and 65536 to succeed, which is the opposite of the spec. This would test that valid input fails and invalid input passes — a backwards boundary test. Answer C looks almost right, but contains a subtle and fatal syntax error: assert.throws(parsePort("65536"), RangeError) passes the result of calling parsePort("65536") rather than a function. This means the error gets thrown immediately during argument evaluation, before assert.throws can even intercept it — your test crashes instead of passing. Answer D uses the correct structure but asserts the wrong error type. The spec explicitly says RangeError should be thrown, but D checks for TypeError. Even if the test passes structurally, it's testing the wrong thing entirely. Study tip: Whenever you use assert.throws, always wrap the call in () =>. If you forget the arrow function, the error escapes the assertion and your test blows up unexpectedly.
  5. Which assertion is the most appropriate test of this result?
    Answer: assert.ok(Math.abs(addTax(0.1, 2) - 0.3) < 1e-10); Whenever you see a question about testing floating-point arithmetic in JavaScript, remember one foundational fact: numbers like 0.1 and 0.3 cannot be represented exactly in binary floating-point. This means 0.1 + 0.1 + 0.1 doesn't produce exactly 0.3 — it produces something like 0.30000000000000004. Testing for exact equality against 0.3 will therefore fail even when your function is logically correct. This is precisely why C is the right answer. Math.abs(addTax(0.1, 2) - 0.3) < 1e-10 checks that the result is within a tiny tolerance of the expected value — close enough to confirm correctness while allowing for the inevitable floating-point rounding error. This pattern is called an epsilon comparison and is the standard approach for floating-point assertions. A fails because assert.strictEqual demands exact equality (===). Due to floating-point imprecision, addTax(0.1, 2) likely won't be exactly 0.3, so this test would incorrectly fail on a correct implementation. B has two problems: toFixed(2) returns a string (e.g., "0.30"), so comparing it strictly to the number 0.3 will always fail — and even if it returned a number, rounding to two decimal places is too coarse for reliable financial logic. D flips the inequality, meaning it passes only when the result is far from 0.3 — it would succeed on a broken function and fail on a correct one. As a study tip: any time you see floating-point arithmetic in a test question, immediately think "epsilon comparison" — never exact equality.
  6. Which pair of assertions would detect both suspected defects?
    Answer: assert.strictEqual(shippingFee(50), 0); assert.throws(() => shippingFee(-1), RangeError); When writing tests to catch specific bugs, you need to think like a detective: each assertion should act as a trap for a particular defect. Here, the two suspected bugs are (1) using > instead of >= for the free-shipping threshold, and (2) silently accepting negative subtotals instead of throwing an error. To catch defect #1, you need a test at exactly subtotal = 50 — the boundary value. If the code uses subtotal > 50, then shippingFee(50) wrongly returns 5 instead of 0, and assert.strictEqual(shippingFee(50), 0) will fail, exposing the bug. To catch defect #2, you need assert.throws(() => shippingFee(-1), RangeError), which fails if the function quietly returns a number instead of throwing. Answer A combines both of these targeted assertions, making it the correct choice. Answer B misses both defects: shippingFee(51) returns 0 correctly even with the buggy > 50 condition, so it doesn't expose defect #1. It also never tests for the RangeError, missing defect #2 entirely. Answer C catches the negative-subtotal defect with assert.throws, but testing shippingFee(49.99) only confirms that values below 50 return 5 — it never probes the exact boundary where defect #1 hides. Answer D tests the correct boundary value 50, but replaces the error-checking assertion with shippingFee(0), which passes even in a broken implementation — so defect #2 goes undetected. The key strategy: boundary values and error paths are your best friends in testing. Always write at least one test at each boundary (not just above or below it) and a separate test for every expected exception.
  7. Which pair of assertions distinguishes correct behavior from both suspected defects?
    Answer: assert.strictEqual(isPalindrome("A man, a plan, a canal: Panama!"), true); assert.strictEqual(isPalindrome("Java"), false); When testing a function for multiple suspected defects, your goal is to write assertions that collectively rule out every defect — not just confirm one happy path. Here, you need to catch two bugs: (1) failing to normalize input (ignoring case and non-letter characters), and (2) always returning true for nonempty strings. Option D is the right pair because each assertion targets a different defect. The first assertion — isPalindrome("A man, a plan, a canal: Panama!") returning true — only passes if the function correctly strips punctuation/spaces and ignores case before checking. A buggy version that skips normalization would fail this. The second assertion — isPalindrome("Java") returning false — catches the "always returns true" defect, because a function blindly returning true would fail this non-palindrome check. Together, these two assertions form a complete diagnostic pair. Option A uses two non-palindromes, so it would catch the "always true" bug but does nothing to test normalization. Option B uses two simple palindromes with no special characters or mixed case, meaning a function that never normalizes input would still pass both — it doesn't expose defect #1 at all. Option C uses two inputs that should return true, which means a buggy implementation that always returns true would pass both assertions without being caught — it completely misses defect #2. The key strategy here: when testing against multiple suspected defects, each defect needs at least one assertion that would fail if that defect exists. Make sure your test suite has inputs that exercise every failure mode, not just inputs that confirm correct output.
  8. Which test correctly verifies both the returned value and the non-mutation requirement?
    Answer: const input = [3, 1, 2]; const result = sortedCopy(input); assert.deepStrictEqual(result, [1, 2, 3]); assert.deepStrictEqual(input, [3, 1, 2]); When testing a function that promises to return a transformed copy without touching the original, you're really verifying two separate contracts: correctness of the output, and immutability of the input. You need the right assertion tool for each. assert.deepStrictEqual compares the contents of two arrays element by element — exactly what you need when checking whether an array holds specific values. assert.strictEqual, by contrast, checks reference equality (i.e., whether two variables point to the same object in memory), which is almost never what you want when comparing array values. Answer A is correct because it uses deepStrictEqual to confirm result holds [1, 2, 3], then uses deepStrictEqual again to confirm input still holds its original [3, 1, 2]. Both contracts are tested with the appropriate tool. Answer B fails because assert.strictEqual(result, [1, 2, 3]) compares references — result and the inline literal [1, 2, 3] are two different objects in memory, so this assertion will always fail even when the values are correct. Answer C is logically backwards. assert.strictEqual(result, input) checks whether result and input are the same array reference — but a correctly implemented sortedCopy should return a new array, so this test would pass only for a broken implementation. Answer D checks that input now equals [1, 2, 3], which would mean the function mutated the original array — the exact bug you're supposed to be guarding against. Study tip: Always pair a deep-equality check on your output with a deep-equality check confirming the original input is unchanged — that two-assertion pattern is the standard way to test non-mutation.
  9. Which single assertion is most likely to expose either suspected implementation defect?
    Answer: assert.deepStrictEqual(uniqueStable([2, "2", 1, 2]), [2, "2", 1]); When testing a function for multiple suspected defects, the most powerful assertion is one that can catch either defect independently. Here, the two defects are: (1) converting values to strings before comparing (which would treat 2 and "2" as duplicates), and (2) sorting the output instead of preserving insertion order. Option A, uniqueStable([2, "2", 1, 2]), is the strongest test because it attacks both defects simultaneously with a single input. A buggy implementation that stringifies values would collapse 2 and "2" into one entry, producing the wrong length. A buggy implementation that sorts the result would return [1, 2, "2"] instead of [2, "2", 1] — wrong order. If either bug exists, this assertion fails. That's maximum diagnostic power from one test case. Options B and D both use [2, 2, 1] and [1, 2, 2] respectively — inputs with only numeric duplicates. These will pass even if the implementation silently coerces types, because there are no mixed-type values to expose the stringification bug. They only partially probe the sorting defect, and neither catches the type-confusion issue at all. Option C uses [2, "2"] with no actual duplicates to remove, so it exposes the type-coercion bug (a bad impl might remove "2") but tells you nothing about whether the output order is preserved correctly, since with only two elements and no duplicates removed, ordering is trivial. Study tip: When choosing a test assertion, look for inputs that act as combined stress tests — a single case that triggers multiple failure modes at once is far more efficient than separate narrow tests.
  10. Which assertion exposes the suspected defect while remaining consistent with the specification?
    Answer: assert.strictEqual(greet(""), "Hello, !"); When testing a function's behavior, you need to think carefully about the difference between falsy values and undefined. In JavaScript, the || operator returns the right-hand side whenever the left-hand side is falsy — and falsy values include undefined, null, 0, false, NaN, and the empty string "". The suspected implementation name || "Guest" has a subtle flaw: it treats the empty string as falsy, so greet("") would evaluate "" || "Guest" as "Guest", producing "Hello, Guest!" — but the spec says every non-undefined value, including "", should be inserted directly. The assertion in B, greet("") === "Hello, !", is exactly what the spec demands, and it's precisely what the buggy implementation fails to produce. This makes B the assertion that exposes the defect. A and C both pass cleanly even with the buggy implementation. Calling greet() or greet(undefined) gives undefined || "Guest" = "Guest", so "Hello, Guest!" is returned — matching the spec perfectly. These assertions confirm correct behavior but reveal nothing about the bug. D is similarly harmless: "Ada" || "Guest" = "Ada", so the output is "Hello, Ada!" as expected. None of A, C, or D stress-test the falsy-coercion problem. Study tip: Whenever you see || used for default values, immediately ask yourself: *"What happens with other falsy inputs like "" or 0?"* If the spec requires those values to be treated as valid inputs, || is the wrong tool — ?? (nullish coalescing) is the correct choice, since it only falls back on null or undefined.

Start this quiz