Rooster Regulation - anonymous function call - why?

I am working on the Rooster Regulation excersise., following up with the video walkthrough.

I do not understand why, in order for the method .timeAtDown tests to work:

  • When it comes to test if the method returns a string, the method is called directly:
    const actual = Rooster.timeAtDawn(inputNumber);

  • But when it comes to test if the method returns a RangeError, the method needs to be called using an anonymous function
    const actual = () => Rooster.timeAtDawn(inputNumber);

I don’t understand why in the second case, an anonymous function call is needed.

Thanks.

Going to have to work through that project (haven’t yet) but one would think that if we log ‘actual’, we should get,

console.log(actual)  //  [Function: actual]

All that tells us is that the .timeAtDawn() method exists.

It may be easier to answer my question if I replicate here both the program and the test.
As you can see, the method Rooster.timeAtDown() is called differently for test returns its argument as a string" than for “throws an error if passed a number greater than 23” and “throws an error if passed a number less than 0” (the latter two using an anonymous function).

//index.js
// Define a rooster
Rooster = {};

// Return a morning rooster call
Rooster.announceDawn = () => {
  return 'cock-a-doodle-doo!';
}

// Return hour as string
// Throws Error if hour is not between 0 and 23 inclusive
Rooster.timeAtDawn = (hour) => {
  if (hour < 0 || hour > 23) {
    throw new RangeError;
  } else {
    return hour.toString();
  };
}

module.exports = Rooster;
index_test.js
const assert = require("assert");

const Rooster = require("../index");

  describe(".timeAtDown", () => {
    it("returns its argument as a string", () => {
      //Setup
      const inputNumber = 12;
      const expected = "12";
      //Exercise
      const actual = Rooster.timeAtDawn(inputNumber);
      //Verify
      assert.equal(actual, expected);
    });
    it("throws an error if passed a number greater than 23", () => {
      // Setup
      const inputNumber = 26;
      const expected = RangeError;
      // Exercise
      const actual = () => Rooster.timeAtDawn(inputNumber);
      // Verify
      assert.throws(actual, expected);
    });
    it("throws an error if passed a number less than 0", () => {
      // Setup
      const inputNumber = -1;
      const expected = RangeError;
      // Exercise
      const actual = () => Rooster.timeAtDawn(inputNumber);
      // Verify
      assert.throws(actual, expected);
  });
});
});

It looks like .equal() takes a callback, and an expected value. Can you view the source code for assert.equal to confirm this?

Thanks @mtf ,I’m afraid I am not sure how what do you mean by “source code of assert. equal”. If I understood well, assert is a node library, and equal one of its functions.
Nevertheless, thanks to your suggestion I noticed that the test using assert.equal is expecting the .timeAtDown method to return something, while assert.throw is expecting .timeAtDown to throw the custom RangeError. This may be the reason why an anonymous function is needed, but still not obvious to me.

2 Likes

Looks like I was wrong. The .equal method takes two similarly (one would expect) typed objects to compare. So, the actual() function must be handed in with an argument.

https://nodejs.org/api/assert.html#assertequalactual-expected-message

At any length, this is not my path, though I will take a trip down the rabbit hole in due course of time. Work your way slowly through that project, and be willing to backtrack or segue to documentation/extra reading and study as you progress.

Asserts can help in testing code.

In the case of assert.equal, we hope that the method being tested (with valid input) executes to completion and returns a value without throwing any errors. assert.equal compares the returned value to an expected value. If the assert fails, then we know that something is wrong with our code (perhaps in logic or syntax or something else).
Have a look at the slightly modified version of your code for assert.equal,

const inputNumber = 12;
const expected = "12";
const actual = Rooster.timeAtDawn(inputNumber);
console.log(actual);
console.log("Before Equal Assert");
assert.equal(actual, expected);
console.log("After Equal Assert: Success");

// Output:
// 12
// Before Equal Assert
// After Equal Assert: Success

In the above code, we are assigning the result of our method call to the constant variable actual, i.e. in the line:

const actual = Rooster.timeAtDawn(inputNumber);

actual has the value "12" as confirmed by the console.log statement. If for some unexpected reason, timeAtDawn throws an error, then it isn’t the assert statement alerting us to the error. We don’t even get to the assert statement. In the above code, the string “Before Equal Assert” will not be printed, because the error will be thrown during the call to timeAtDawn. If an error gets thrown even for a valid input, then we know something is wrong with the code.
If no error is raised, and we get to the assert statement, then at least we know that the method returned some value (whether correct or incorrect remains to be determined). If the assert is silent, then we pass the test successfully. Suppose const inputNumber = 12; and const expected = "12" but timeAtDawn returns say "10" instead of the expected "12". then timeAtDawn won’t throw an error. But, the assert.equal will throw an AssertionError alerting us that timeAtDawn failed to return the expected output.

Contrast this with the assert.throws scenario. When an invalid input is passed to timeAtDawn, it is supposed to throw an error. This is by deliberate design. But, for testing, we don’t want to throw an error which halts execution of the test program. If timeAtDawn throws an error for the reason expected by us, then assert.throws silently signals that all is well, the assertion has passed and we don’t need to halt the program. So we can move on to test other assertions or run more code.

const inputNumber = 26;
const expected = RangeError;
// const actual = Rooster.timeAtDawn(inputNumber);
const actual = () => Rooster.timeAtDawn(inputNumber);
console.log(actual);
console.log("Before Throws Assert");
assert.throws(actual, expected, "Throw Assert Failed");
console.log("After Throws Assert: Success");

// Output:
// [Function: actual]
// Before Throws Assert
// After Throws Assert: Success

In the above code, look at the commented out line

const actual = Rooster.timeAtDawn(inputNumber);

This is the same as we did for assert.equal. Since the input is invalid, so timeAtDawn will throw a RangeError and the program will halt. We could look at the terminal to see details about the error, but it defeats the purpose of automated testing as opposed to human inspection. timeAtDawn was designed in such a way that out of range inputs would cause an error, so this is not aberrant behavior.

By using,

const actual = () => Rooster.timeAtDawn(inputNumber);

we haven’t invoked the function yet.
console.log(actual) shows [Function: actual] as the output, as opposed to the assert.equal case where actual stored the value returned by timeAtDawn.
assert.throws will execute the function call in actual. An error will be thrown by timeAtDawn, but assert.throws will compare this error to the expected error. If the errors are the same, then the assert is true and timeAtDawn threw an error exactly when we wanted it to and for the reasons we wanted it to do so. The program will not halt, the string "After Throws Assert: Success" in the above snippet will print and we will move on to test other assertions or run other code.
Suppose the expected error was RangeError, but timeAtDawn threw a TypeError instead. Then, the assert will fail. The thrown error was raised for unanticipated reasons, so the assertion is false. The program will halt with the error shown in the terminal. In this case the program didn’t halt because of timeAtDawn throwing an error, but because the assert failed due to a mismatch between the actual and expected errors.

2 Likes

Thank you so much for such a thorough answer! It took me a good 45 mins of reading, and to review the complete testing lesson once again, so I could understand the whole extent of it. But the effort was worth it :slight_smile:

1 Like

This topic was automatically closed 41 days after the last reply. New replies are no longer allowed.