- Client-side web applications,
typically called REST services on the server,
to get and modify data from the server.
So the code that invokes the REST service
is typically asynchronous on the background thread,
because it might take a few seconds
for the REST response to come back.
So when you invoke the REST service,
you typically get back a Promise object immediately,
but that Promise object is a pending result.
You won't actually get a value inside that Promise object
until the REST service has completed.
So the Promise object, basically,
will either resolve eventually with the actual result
or it'll reject with an error.
And what you have to do somehow in your tests
is deal with that.
How can I take the Promise object,
and either take the result
if it's been resolved successfully
or handle the error if it was rejected?
So we're gonna see how to do that in this section.
We're going to see how to mock functions
that return a Promise.
The last thing that you want to do when you are unit-testing
is to actually call the REST service,
because it's gonna be too slow.
You don't want to wait five seconds
for the Promise to resolve.
You can mock that instead,
and you can mock whether the Promise
is rejected or resolved.
So imagine this hypothetical situation.
I've got someFunction and someModule
and it returns a Promise object.
In other words, here is a Promise object
which will eventually either be resolved with a result
or rejected with an error.
So what we could do is we could mock this module.
We could import the module, which I've imported someModule,
I've called it m.
And then I can create mocks
for all the functions in that module.
So I've mocked someFunction
and then I can mock the return value.
When you have a function that you mock like this,
remember when you call jest.mock,
it'll create a mock of all the functions.
And by default, the mock will return undefined.
So what you can do is you can say this module,
which I've just imported,
that has a function called someFunction.
I've mocked that function here.
What I'm gonna do is I'm gonna mock its return value.
I'm going to say that that function when it returns,
it'll return a Promise,
I'm going to mock that that Promise
will resolve with this result.
So you're not just mocking the fact
that it returns a Promise.
You're mocking the fact that the Promise object
actually succeeds and here is the eventual result
of that Promise, as if it's the real data
that's come back from the REST service.
And also, I can say, this function that returns a Promise
or that when I mock it doesn't return anything,
but I can mock it to return a Promise that is rejected.
As if we'd called the real function
that return a Promise and that Promise had rejected,
that's what we're mocking here.
Mock the fact that the function would've been revoked,
returned the Promise and that Promise was rejected,
basically, with an error.
Well, let's have a look at a concrete example of that
to see how it all hands together in practical terms.
So have a look, if you want to, again
if you wanna follow along,
go into the MOCKING_PROMISES folder.
I've got several code files in there
if you wanna take a look.
So I have two main Java files.
I've got a Java file
which encapsulates calls to a REST service.
We'll have a look at that first.
And then I've got another JavaScript file
which is like in front of it.
So this is my logic and it calls the REST client
to interact with the backend.
This will have functions that return Promises.
I will grab those Promises here,
take the results and do some work on them.
Once we've seen how that works,
then we'll see how we can write tests
to mock those Promises.
So we'll take this step by step.
So first of all, we'll have a look at the REST client.
So here's my REST client.
So this is the picture that we've got.
At the backend, there is a REST service.
So that's running, maybe's implemented in Java Spring Boot
or Web API.
That's my REST service at the backend.
And this API on the client has functions
that basically invoke that RETS service at the backend.
So it's a hypothetical online retail store
where we can get information about products.
So I've got a function here, get all products.
This is in my REST client API.
So I'm gonna draw a box here.
This box here represents my REST client.
I'm gonna draw this picture a few times
in the next few minutes as we progress through
just so you can see the progression.
So I've got in here a function called getAllProducts,
getAllProducts.
So I'll draw that function here.
I just call it, getAll just to give myself a bit more space,
getAll.
So when I call that function,
what this function does is uses axios.
So axios is a standard,
fairly useful library for calling REST services.
So you can say, I wanna make a getRequest to a URL.
I've got a hypothetical URL up here.
And the idea is that it returns me a collection of products.
So the thing is the get function returns a Promise
and it will take a while to complete it.
It'll take a while for that Promise object to be returned
from the REST service.
When that has been returned,
the response you get here is the entire HTTP response body
back from the server, including headers, status code,
and the body itself.
So when that data has been returned,
what we then do is to take the HTTP response
and just get the HTTP body
which is the actual data that we want.
So if you follow this through,
if this function returns a Promise
then function also returns a Promise.
So the net result is what I call getAllProducts
for my other code.
We'll see that in a minute.
That will return a Promise.
So this code here is going to return a Promise
that will eventually contain all products.
And then a similar function here,
which I won't go into, but it's the same idea.
Instead of getting all products, similar idea,
but this function gets the product with a particular ID.
So it issues a getRequest, products slash ID like one.
And hopefully, that will return the data
for just one product
which we then return back via a Promise.
So this REST client, it provides functions
which return a Promise
and I'm going to use that API in my higher level code.
That's the next function
or the next file we're going to look at.
So note this here that both of those functions
return a Promise and now we're going to use it here.
So I'm going to just draw the backend again.
This is my REST client that we just looked at.
So draw that backend REST client.
And then it had two functions, getAll and getById.
We just imagine those there,
which call into my REST service, which is a blob like that.
So we've seen that already.
And what we're gonna do now is we're going to look
at a higher level code that sits in front of it and uses it.
And this is my operations.js.
So operations, my higher level code
that uses the REST client as a service provider,
basically, operations.js.
So what I'm gonna do is I'm gonna have two semantic methods,
one that will return the total stock count.
So imagine I've got various products in my catalog.
I've got four of one product,
three of another product,
and five of another product.
What this function's meant to do
is to return the total number of products
I've got all together.
So if I've got five of that product
and three of that product and four of that product,
that's gonna be five plus three plus four, that'd be 12.
That's what this function is hoping to do.
So if we just have a look then.
We are going to call the getAllProducts method.
So this here, I'll call this,
this is my getStockCount function,
getStockCount.
Hopefully that you can read my writing there.
That's my best writing.
So when I call the getStockCount method,
the first thing it has to do on my REST client,
it calls getAllProducts.
So the first thing it does
is to call getAllProducts like that.
It returns immediately.
That function returns immediately with a Promise object.
That Promise object will eventually contain
a bunch of products, eventually.
So what I say is take that Promise object
or when it has resolved, assuming that it succeeds,
when it is resolved then this parameter here
will be the products, the result, basically.
The products array inside the Promise.
The result in the Promise object,
that is what's going to that's that.
Basically, that is this.
So what we then do, I've got some code here,
which I was quite pleased
when I figured out how to make this code.
It takes that array of products.
Imagine that each product has a stock property.
So it says, take that array of products.
So that's my array of products here.
And for each product, so P would be one product.
Give me just the stock value.
So maybe five is the first stock,
three is the second stock and four is the third stock.
So that just give me a bunch of stock numbers.
And then I call the reduce function.
If you haven't seen the reduce function before,
it's very cool.
What it does, it kind of takes two parameters
and reduces them to one result.
So the first parameter is like the ongoing total.
This here is the initial value for that parameter
just to bootstrap the process.
This here is the initial value for that parameter.
And next, that's basically the first value from our array.
So from this array, which let's say that array cabinet
contains stock numbers like five and three and four.
That's a bit difficult to read
but hopefully you get the idea.
Next would be the first value from there.
And what I do is I return the current total,
which is zero plus the next value,
and that gives me back the new aggregate total.
So that would say zero plus five is five.
What it then does, is it repeats the process it says.
So that is the total so far.
And then it takes the next value from there.
And it passes in and it says five plus three,
well, that'll be eight.
So that's our new total, eight.
And then that becomes the value
for the next incarnation like that.
And then it'll grab the next value from here
and pass it in like that.
So as you can see, what this is going to do?
Basically, is it's going to take the stock of each product
and then add it, add them together.
It'll give me the total stock,
assuming that the Promise was resolved successfully.
'Cause what could have happened is the Promise
could have actually been a rejection
in which case I have a catch.
And in this case, I just returned zero.
If I can't call the REST service,
then instead of returning a proper stock value,
I just return zero as a suitable fallback.
So hopefully that makes sense.
Have a look at the code and, and have a play with it
if you just wanna make sure.
The code is all there in the demo pack,
just go into the MOCKING_PROMISES folder
and have a look at operations.js.
And the code is there for you to have a play with.
The second function is the similar kind of idea.
Instead of just giving me the stock count
like to say you have 12 products,
it gives me the cumulative total
of everything I've got in stock.
So very similar process, get all the products.
Once we've got the products,
what it does then is it multiplies the price of one product
multiply by the stock of that product.
It's like a line count.
That would give me a total value of one particular product,
which I then add together.
And it'll give me the cumulative stock value
of everything I've got in stock.
If you've got 10 of those product
and it cost a hundred, that's worth a thousand.
So these functions should actually work.
What I wanna do though, is to test them.
I want to test that this function
does actually give me back the right response.
And in order to do that,
I'm gonna have to mock that function.
I'm gonna have to mock that function
so they did either mocks with a result.
In other words, it resolves to a result
or that I'm gonna mock the fact that it marks with an error
that it rejects an error basically.
So let's see the tests.
So again, these tests are in the demo pack,
if you wanna have a good look.
So that's in operations.test.js.
So I import my operations code
and I import my REST client code.
And then what I've done here,
often when you are test and you create mock data,
so I've set up a dummy products collection.
While we're going skiing in a few days, we are very excited.
And so I thought here's an example.
A product, skis.
A pair of skis cost 500 pounds.
And there were 10 of those in stock.
A pair of ski boots cost 200 pounds
and I've got five of those in stock.
Don't forget the goggles.
You need to have goggles when you skiing.
And they only cost a hundred pound, a bargain,
and we've got three of those in stock.
So that's fairly straightforward.
Then what I do is I mock all the functions
in the REST client module.
In other words, I mock all of the functions in here.
I mock that function and I mock that function.
And if you remember, what happens when you mock functions,
when you mock an entire module?
It'll create a mock version of each function
that becomes undefined by default.
So that makes sense so far.
So just to pick out a few points then.
Import the REST client API module,
mock all the functions in that model.
So by default, all those functions return undefined
according to the mock, but I'm going to go a step further
and I'm gonna say, well, actually, you know what,
for the getAllProducts function,
undefined doesn't really cut it.
I'm gonna mock that as if it returned successfully
with the Promise, which resolved.
So, like I said earlier,
you're not just mocking the fact that it returns a Promise.
You go in a step further
and say it didn't return just a Promise,
it returned a Promise that resolved with this result.
So this is the ultimate result
that came back from the REST service.
This is what my mock is achieving.
I'm saying that when I call that function,
it's as if it return a Promise
which resolved to return those products.
As if those products came back from the REST service.
Those are the products that will eventually be passed
into my client code.
So have a look at this.
The test itself is actually the easiest part.
When I call getStockCount,
and remember, that's the rather complicated function
that I explained just now.
Let me give you a quick recap just to remind you.
So I'm calling that function
and that function calls, REST client getAllProducts.
I've just mocked that function to resolve
with a bunch of products.
So basically I've mocked the fact it's going to resolve
as opposed to reject with an array of three products,
ski boots, skis, and goggles.
And what's more?
Those products that I mocked,
I basically mocked this to return that product array.
And if you do a bit of math here,
if you add up the stock here, if you've got 10 of that item
plus five plus three that's 18, isn't it?
So what I'm expecting is that this algorithm,
if it's implemented correctly,
should actually give me back 18
because according to the products that I've just pumped in,
the stock total is 18.
Often you do this when you're testing.
You give us some test data, hard coded,
and you work out what the answer should be
if your code is correct.
If my code is correct, the stock count should be 18.
So here's my expectation.
First of all, when I call that function,
it should have called that function on the REST client.
And also the result that I get back should be 18.
So just some notes.
We call the function under test.
It calls getAllProducts.
Verify that that was called and then check.
And this really is a proper check here, check.
When I was writing my demo code, I did actually rely on this
to make sure I'd implemented my algorithm correctly.
Make sure that the result we got back
is actually the correct result.
18 is the result we expect to get back.
Given the data that we've passed in 10 plus five plus eight,
that should add up to 18.
That's the first test.
The second test, very similar,
but instead of testing the stock count,
I'm testing the stock value.
Similar idea, I call the getStockValue function.
So in my code, I'm now calling this function.
It invokes that and I've mocked that to resolve.
I've mocked that to resolve with those products,
skis, boots, goggles.
So that data is kind of hard coded in my test.
So verify that my function called getAllProducts
while it should have done, shouldn't it?
And then check the logic here to see what value
did it actually return when it multiplied the price
by the stock for all those products.
What value did it give back?
According to my maths,
the value should have been 6,300.
I think that's right.
Let's go have a quick look in my test.
I've got 500 pounds for each pair of skis
and I've got 10 of them.
That's 5,000 pound.
200 pounds for a pair of boots and five of those.
That's another thousand.
So that's 5,000 plus 6,000 plus 300.
So the total value of each line,
the total value should be 6,300,
shouldn't it?
Yes.
So just to verify then,
I call the function-under-test.
It should have invoked this API, which I told it to resolve
with a bunch of products whose results should give me 6,300.
I ran this test and it does actually work.
The final outcome, as well as testing
for Promises that resolve,
you've really got to test for Promises that reject.
So that's what I've done in my final test.
I basically mock, in my mock module.
Just remind you again,
just so that you haven't lost sight of this.
Remember the first thing I did in my test
was to mock the entire module.
When you mock the module,
every function in there is mocked to return undefined.
So initially I mocked the getAllProducts function
to resolve successfully.
What I'm doing in this test is I'm saying I wanted to mock
with a rejected value.
So in other words, it's like a Promise that rejects
with this error message.
So when I call getStockCount what should happen is,
when I say getStockCount,
it should go into the catch block here and return zero.
So verify that the result we got back is zero.
When I call my getStockValue, the result should be zero.
Because when the Promise rejects,
it returns zero as the fallback value really.
Well, that was quite a lot of hard work.
So just a note,
I specify that I want my REST called to fail.
Basically with that error.
When I call the function, I verify,
first of all, that my function did call getAllProducts.
That function was called and it returned a Promise
that was rejected.
So this should have been the result
that I got back from my ultimate function, zero.
So we can run all those tests, take a deep breath,
hope that they work and they work.
So there we go.
So the key point, two key points to summarize.
When you have functions that return a Promise
that will either resolve or reject,
you have to or you might want to mock that Promise
with either this rejected error or this resolved result.
So in your test, mock the module if you want to.
The function that returns a Promise
either mock that it returns successfully with that result
or mock the fact that it rejects with this error.