00:08
because we do need to be a little bit more due diligent and careful with how we test those sad paths where we actually expect the promise to be rejected. So let's start by diving into our terminal
00:18
and I'm just within a new tab within our projects location here and we'll do node ace make test and we'll just call this async so that we know specifically what it is for.
00:28
And I'm gonna put this inside of our unit suite, perfect. And clear that out, jump back over into our watch tab. And again, since this is watching our file system for changes, it has noted that we've added
00:38
a test unit async.spec.ts file. So it's gone ahead and run that and it found our example test that's started within there and that test passed.
00:47
So if we were to dive into our test unit async.spec.ts file, there we are right there with that example test. Now for contacts within our app services,
00:56
there are a number of different service files, particularly here today, we're gonna focus on the test service.ts file. And the service contains a number of different utility methods that will aid us
01:05
in testing various scenarios to get used to them. So today we're gonna be focusing on this get user method in particular, which kind of mimics an asynchronous operation.
01:14
So we take in an ID of type number, it will wait for about 100 milliseconds. If we pass in an ID of one, then we'll go ahead and return back a promise
01:24
with this object in it, containing an ID of one and the name of John Doe. If we were to pass in any other ID, then it's gonna go ahead and throw and user not found exception.
01:33
Great, so back within our async.spec, we can go ahead and start by testing our happy path where it should successfully get a user with an ID of one.
01:44
And I'm gonna go ahead and hide the sidebar away there to give us a little bit more working room. Let's start by instantiating a brand new instance of our test service. So new test service, and we can import that from services test service.
01:54
Then we can go about business as usual and go ahead and grab our user using that test services get user method. We are testing the happy path here.
02:03
And as we saw, the happy path for that expects an ID of one to get back our John Doe user. And that should return back an ID with a value of one.
02:12
So we can do user.id, we should expect one there. And we can also assert that the user's name should be John Doe. So if we give that a save,
02:21
it should change our test name to successfully get a user with an ID of one, and it should also pass. Perfect. Again, there's plenty of different ways that we could go about asserting this.
02:31
Here we've chosen individual equals on a per property basis, but we could also do assert deep equal, pass the user in as a whole object, and then pass in a separate object with an ID of one
02:41
and a name of John Doe, essentially rebuilding that object so that we can assert that that's what we expect user to contain. So if we were to give that a save, we'll see that that also passes.
02:50
So you have either one of those at your disposal, plus a number of different options as well. Great. So what about if rather than using async await here,
03:00
we needed to use chaining for whatever reason, maybe we just prefer chaining as well. So for that, let's copy and paste our test,
03:07
and we'll rename the first one here to async await, and we'll rename the second one to chaining. So we'll show an example on how to do the same test,
03:17
two different ways. So since we wanna use chaining, we'll get rid of our await there, which means we will now get our user inside of the then callback, just like so.
03:26
And we'll then want to move our two assertions up into that then callback. So with this, our get user is gonna return back a promise
03:34
with our John Doe user inside of it. Whenever that promise resolves, our then callback is going to be executed running our assertions.
03:43
Now there's something to watch out here for though, because if we go ahead and run this, you'll see that it looks like everything succeeded a-okay. Our test came through as passing,
03:52
but in actuality, our test isn't actually waiting for our get user promise to resolve, and the then callback with our assertions isn't actually executing until that test
04:02
has already been marked as successful. We can actually see this in action if we switch our ID from one to two so that we then get an exception. So if we give this a save,
04:11
you'll see that our test is still succeeding, even though it's now later on throwing an exception. So the flow we're going through here is we enter in our test, we create our new test service,
04:20
we send out our get user, which returns back a promise. Yappa doesn't know that we expect it to wait for that promise to resolve, so it's continuing onward.
04:29
Since it's continuing onward, it's assuming that this test contained no assertions, so it's marking that test as successful. Later on, after it's already done that,
04:38
our promise resolves, and it's at that point, it realizes that it has thrown an exception, and it captures that exception and prints it later on,
04:46
but that test is already marked as successful. So what we need to do is tell Yappa to actually wait for this promise to resolve before moving on from this test.
04:56
And for that, Yappa provides a done method similar to AdonisJS's router system. So the first argument here is our test context. The second argument is going to be
05:06
that done callback method. Once we've completed our assertions and we're done with that callback, we can then call done just like so.
05:14
Again, relatively similar to AdonisJS's middleware system. We're still not quite done yet though, because the test as a whole doesn't know that we expect it to wait for done.
05:24
So if we give this a save, you'll see we still have the same outcome whenever our test actually runs. What we also need to do is chain off of our test as a whole to tell it to wait for done.
05:33
This is kind of like a flag to enable the actual done method because since we're not using done anywhere else, Yappa doesn't need to actually wait for it. So we want to inform it
05:43
that it does need to wait for it here. So with that saved, you'll see that it kind of waits for a second because it's actually waiting for that done method to be called. However, done is never called
05:53
because ultimately our getUser is going to throw an exception. So what actually happens here is our test times out, and then after it times out, it realizes and throws that exception as well.
06:03
So we have both a user not found exception and a timeout exception because done was never called. So now that we know that we're no longer getting a false positive there,
06:12
we can switch this back to an ID of one that does succeed and we're a-okay. Perfect. So when you're using chaining, be sure to grab the done method here
06:21
from the second parameter of our test callback, call it after you're done with your promises callback. So after you've done your assertions within that callback,
06:29
and then also inform the test to wait for that done method before continuing onward. Okay, let's now move on to our sad paths.
06:36
So test, and we would expect this to throw an exception when user is not found. And we'll take a look at this one a couple of different ways as well.
06:45
So we do a try catch with this one. So we'll do async and grab asserts out of our test context, and we can go and grab our new test service.
06:54
So new test service, just like so. And for this one, we're going to be testing with an ID of two. So we'll await test service,
07:03
get user ID of two, or really could pass anything except for one there to test your sad path. Since we expect this to throw an exception,
07:11
we're going to want to go ahead and capture that error so that we can assert against it. So we can go ahead and assert that we expect the error to include.
07:19
So for our haystack, we'll pass in the error, and for the needle, we'll pass in an object with a status of 404, and we'll start there. So we'll go ahead and give that a save,
07:29
and we should see that it passes because ultimately our get user method is going to throw a 404 exception whenever it sees that we've passed in an ID of two instead of one.
07:39
If we want to expand on what we're asserting here further to include more of our errors properties, we can go ahead and console.log the error so that it prints out into our terminal whenever that's run.
07:49
Perfect, so we see here, we have a code of E, user not found, and a status of 404. Then because we're in development, we have a stack trace there as well,
07:57
and then we have our actual exception of user not found. So in addition to including the status within our assertion,
08:05
we can also pass the code in there of E, user not found, and we can do the message as well if we wished, which is just user not found.
08:14
All right, I'm going to remove the console.log. Let's give that another save, and we should see that that, nope, it failed again. Oh, I did U, user not found, not E, user not found. There we go.
08:24
E is short for an exception code. There we go. So now we get that passed. However, as I mentioned in the introduction to this lesson, we need to pay extra attention,
08:33
particularly within this catch here, because if our get user doesn't actually throw an exception, which we particularly expect for this test,
08:42
then ultimately what we're going to get is a false positive. So you'll see here that our test still passes whenever we pass an ID of one in, which actually returns back our user
08:52
and does not throw an exception. So what's happening here is we're actually never getting to our catch. So if we console.log here, give that a save, that's never going to print out
09:01
because we're never actually inside of the catch, never making our assertion, but because there's no assertions then within the test, Japa is going to mark that as successful.
09:10
If we switch this back to two, which would throw an exception, you'll see that we then see our here message as confirmation that we are actually getting inside of that catch. So for that reason,
09:20
using a try catch to make our assertions isn't particularly the best approach at our disposal. If we go ahead and give our test here a copy
09:29
and give it a paste, and I noticed I missed my end parentheses right here. So I'm just going to add that back in. Rather than using a try catch, what we can instead do is assert
09:38
that the promise is rejected. So if we go ahead and get rid of everything inside of our try catch there, what we can do is await
09:47
and off of our assert is a method called rejects. And within here, we should perform our async operation inside of the first argument as a callback. And this will assert
09:57
that the promise inside of that callback is then rejected. So if we do our test service.getUser inside of here and pass in our ID of two, which will throw our user not found error,
10:07
this assertion will then pass because our promise within our getUser is rejected. So if we give this a save, there we go. We see that that succeeds,
10:16
meaning that it is indeed getting an exception or error caught there. And our promise is rejected. If we switch this to an ID of one, which is the false positive scenario, which is covered with our try catch,
10:26
you'll see that this then notices that the exception was not thrown. The promise was not rejected and our test properly fails at that point
10:35
because we're specifically expecting it to throw an exception. So this is our intended and desired behavior. This rejects method also optionally accepts
10:43
in a error message that we can expect with this as well. So if we pass in our user not found and switch this back to our user ID of two or something other than one,
10:53
so that we actually get our exception thrown, you'll see that that still is passing because our message is a match for the actual exception that's thrown.
11:01
If we tack on an S or something like that there so that the message doesn't exactly match, you'll see that that now goes to failing because we're not getting the expected error message.
11:11
And this can also be rejects as well if you have say a long error message or maybe you only wanna match a particular portion of it. Great.
11:20
Finally, we covered chaining with the successful path. What about with our error path? So we can go ahead and copy this and plop it here and we'll just do this as our last test within the spec.
11:29
So we should expect this to throw an exception when user is not found and we'll switch our get user ID from one to two.
11:39
And since we now expect this to throw an exception, we want to catch that exception with our promise and we no longer get our user, but now we get back the actual caught error
11:49
that we will then want to assert against. So we can assert equal and this is gonna be the exact same error that we have up here inside of our try catch. So you copy that verbatim
11:59
or I'll use this as an example that we can do this on a per property basis here with equals. So we should expect our status to be 404,
12:07
should expect our error code to be E, user not found and we should expect message,
12:15
so error message to be user not found. Okay, so looks relatively similar to our passing or happy chaining test.
12:25
The only difference is then is now catch and rather than asserting against our user, we're now asserting against that error. So if we give this a save, all's good there,
12:34
except I spelled when wrong, so I'll fix that. And unlike with our try catch issue up here where we can have the false positive if our test did not throw an exception,
12:43
this one won't be particularly bound to that exact same issue. So if we switch our get user from an ID of two to one, give that a save, you'll see that
12:52
that kind of paused there for a second and ultimately resulted in our test not passing and it didn't pass because it timed out. It timed out because done was never reached.
13:02
We told the test to wait for done but we never actually called done because we're not throwing an error, our catch is never executed and our done is then never called.
13:11
So although the test doesn't fail for the reason that we would expect, it still informs us that what we expected to happen didn't actually happen.
13:19
So it's not quite a false positive in that sense. It's still telling us that we need some attention here. So we'll switch that back to how we intended it to be
13:28
by throwing an exception and we're all back to green now. Perfect.