- In industrial-strength code,
it's important that your applications detect errors
and throw exceptions and you can write tests
to verify that happens.
So, consider these three aspects of error handling.
First of all, you might want to define an error class
to encapsulate information about what's going on.
So the example we are going to look at is a simple bank,
simple example,
where if the user tries to withdraw too much money
and error occurs.
So we're gonna define an error class
to encapsulate information
such as the amount they tried to withdraw,
when they tried to do it,
which ATM machine they were at when they tried to withdraw,
that kind of information.
So define an error class
to store information about the problem.
And then in your actual code throw an error
when you detect an anomalous situation, insufficient funds.
And then more importantly
for this video series is how to test for that.
How can you test your code
to make sure that an error has been thrown
when it should be?
Okay. So we're gonna see all aspects of this
in a simple example is in the testing for errors folder.
So we'll first of all, take a look at that.
In the code folder, JsTdd Lesson 03 testing for errors,
that's the code folder.
So if you open that folder up
in the in the code editor, here we are.
In my accounting js, I've defined two classes.
I've defined an error class
and I've defined the actual business class itself.
Okay. So my error class extends error.
That's a common practice
when you are defining custom error classes.
Error is a built in class in JavaScript.
So that's my error class.
We'll have a look at that first
and then we'll see how to use the error class
in my bank account code later.
So first of all, the error class,
this is what the error class looks like.
I've call it bank error.
It's common to call your class you know, something error.
Extend the built in error class in JavaScript,
define a constructor.
Every error has a, a string message.
So take a string message as a first parameter
and then pass it to the super class constructor,
the super class error.
The constructor takes the error message
and stores it internally
and then any other parameters you want.
So in our bank error,
it would be useful to store amount of money
that the user was trying to withdraw, for example.
And probably convenient
to have some kind of two string function
which returns a textural representation of the error message
plus other information as well.
Okay.
So that's the, that's my error class in accounting js.
Next step,
is to make use of that error class in my bank account.
So here's my bank account code.
I've got a static overdraft limit.
Anybody can go overdrawn up to a thousand pounds, let's say.
A constructor to initialize the details
for this bank account, and the person's name,
and the account balance is zero initially.
The deposit function takes a balance
takes an amount and adds it to the balance.
That's simple.
The withdrawal function, a bit more involved.
Calculate what the balance would be
if we subtracted the amount.
And if the balance is too negative,
so less than minus a thousand, then we throw an error.
Okay. So that's the error class we just looked at.
That's the error message, insufficient funds.
That's the amount that the user was trying to withdraw.
If the amount wasn't too large
then we just proceed and subtract it from the balance.
Okay. So that's my bank account class.
And you can see the bank account class here,
in my accounting js.
Okay. So next then, then how can we be sure in our tests
how can we validate the fact
that the error was thrown when expected?
So, here is my test suite in accounting test js.
I'm gonna go through several different levels of rigor
when it comes to checking for errors.
Okay. So first of all, import the accounting module.
When I import the accounting module
that will have a class called bank account
and a class called error.
So the bank account class is in the accounting module,
which I've just imported.
So I've got a test suite.
And before each test in my test suite,
I create a new accounting object.
It's important with unit testing
that your unit tests are independent.
So every test should have its own new bank account object.
So I've created a bank account object there,
that's the name of the folder.
And just to get the ball rolling,
I deposit 2000 pounds into that person's bank account.
Thank you very much.
And what we then got is that 1, 2, 3, 4, 5 different tests,
which we're going to look at in the next few minutes.
First of all,
if the person, if Emily has 2000 in her account
and she tries to withdraw a small amount
then we'll test that that works okay without any errors.
Then we'll see if we try to withdraw too much,
that it should cause an error.
And we'll then see
four different levels of checking for errors.
Okay. Depending on how rigorous you want to be.
So first of all then,
this is our first test where it's common usually
when you're writing tests that you check
for the kind of all good scenario first.
Okay. So that's the first test.
If I tried to withdraw a small account,
that should work okay.
So if the person's account balance is 2000,
and we tried to withdraw a hundred, then that should succeed
and the balance should be 1900. No problems at all.
That's the first test.
Then we start looking at error conditions.
So if you wanna test an error occurs,
if you just wanna test that the error occurred
but you didn't want to, you know
test the details of the error.
Like did the error happen? And that's enough.
This is the easiest way to check for errors.
What you do is you call the expect function
and you give it a Lambda.
And in this Lambda, you basically invoke the method
that you think will raise the error.
And then you check that, you know
this Lambda did cause an error to be thrown.
So this is the simplest way of checking
if an error occurred.
when this Lambda was invoked,
did the code inside that Lambda cause an error to occur?
We haven't checked what type of error
or what message or anything like that.
We've just verified that
the withdrawal function did throw an error of some type
if we tried to withdraw too much money.
So that's the simplest way of checking for an error.
If you wanna be a bit more thorough,
then you can test for a particular type of error.
So you can say, okay,
inside my expect function pass a Lambda with some code
that we expect to throw an error.
And then when you call to throw
you can specify what type of error you expect.
It could be the case that your function and a test
threw different types of error, for different reasons.
We can check
that the particular error that it threw was the bank error.
Remember bank error
is the class defined in our accounting module.
So you can check
which particular kind of error did it throw.
Right. You can go further and actually check the message.
You can say, okay, here's my code.
I'm expecting it.
I'm testing that it threw an error.
And this is the error message.
And the previous example we checked for the error type.
Now we're checking for the actual error message.
Maybe you want to verify
that a particular error message was thrown in the error.
Okay. And if you want to test for a precise error object,
you know, so you basically test all details about the error.
Then you can be as precise as you want to be.
So what you do is
you basically call your method in a try block
and then catch the error object,
and then assert on the details of the error object
in whatever ways you want to test.
So here's an example,
this is the most specific that you can be.
So I'm I've got a try block where I call some code
which I expect to throw an error, catch the error
and then verify in as much detail as you want,
if you need to.
First of all, I'm expecting the error
to be, the error object, to be an instance of that class.
Was it this type of error that occurred?
I can also check the error message,
get the message property from the error object
and check that it's this string.
In my case, my error object also has an amount property.
So I can check that the error amount is 3001.
That's the amount that we were trying to withdraw.
So if you want to, you can be as precise in error handling
as, as you, as you like.
Okay. So I'm going to run the code and verify
that when I expect errors to occur, they do occur.
So let's run the tests and, and let's see what happens.
Okay. So here I am in my command window.
So NPM round test, and then parameters,
the name of the file,
the name of the test file was accounting.test.js.
Give it the file name.
So it'll run all five test functions.
One test function where the withdrawal was okay
and four test functions where the error wasn't okay.
And an error occurred
and it successfully, you know, caught those errors.
So there we go.
So in the case where no error occurred or was expected,
that was fine.
In the four cases where an error was expected,
my code did throw an error.
So as far as my tests are concerned it they've succeeded.
My code successfully threw an error when expected
and that's what we were hoping to prove.