- In this section, we're going to see how to test classes.
And we're also going to see how to mock classes as well.
So if you have a look in the folder,
Mocking Classes AutoMocks.
If you have a look at the code in there
you find we have two classes,
we have a low-level class, RestClient
and it invokes rest service methods on the web server.
And then we have a higher-level class, ProductManager.
ProductManager has higher-level methods
like get stock count and get stock value.
When I call methods in ProductManager it will invoke methods
on RestClient underneath the surface.
So let's take a quick look at those classes first.
So this is the folder to go into, Mocking Classes-AutoMocks.
If you open that up in the code editor,
here we have the code.
So first of all, my RestClient class
it has various low-level methods, but first of all,
it has a "constructor" the URL of the rest service
that it's gonna call, and then it has methods
to delete an item on the on the rest service,
to update the product with new data,
to get all products and to get one product.
These are quite low-level methods.
And what I'm gonna do is I want to mock these methods.
When I'm testing my ProductManager class,
it makes use of RestClient.
When I call methods on my ProductManager,
I wanna test these methods, but I want to blank off
how it interacts with the low-level object.
When you do unit testing, you're testing one class at a time
and you want to mock how it interacts with other objects
because that would just interfere with our tests.
So that's what we're going to do.
We're gonna see how I can define mocks for the RestClient
API so whenever I call a high-level method,
it'll call a mock version of the RestClient method.
If I call this function in my test,
it'll call a mock version of that method.
If I call this function in my test,
it'll call a mock version of that method.
So I'm gonna basically mock,
I want to mock everything in the RestClient module
so that it doesn't complicate my tests.
Okay, so we've seen the ProductManager class
it uses the RestClient class.
I'm going to unit test ProductManager,
therefore I'm going to mock the RestClient
so that it doesn't interfere with my high-level tests.
So have a think about this,
how can you mock an entire class?
Well, the easiest way to do it is via automatic mocking.
We've seen this before actually.
If you think about it the RestClient class
defines a bunch of methods.
If I go back into RestClient, it defines a constructor
and a bunch of methods here.
So there were various functions defined in this module
and we've already seen how to define mocks
for an entire module.
What you do is you call jest.mock
and you specify the name of the file,
or the name of the module that you want to mock.
And if that file defines a class
then when you mock this module
it'll define a mock version of every method in that class.
It'll define a mock version of the constructor
and a mock version for all
the methods in the class automatically.
So just by mocking the file,
I'll automatically generate a mock version
of the construction, the delete update,
get all, and get methods.
Okay, so I can then use those mocks in my unit test.
So this is a simple example, first of all.
First of all, I've imported the RestClient module
and I've mocked it, so I've justified mock versions
of the constructor for RestClient
and the update, delete, get, get all method.
So basically I have a mock version
of the constructor, for example.
When I create a RestClient object it'll call the constructor
but it basically calls the mock version of the constructor.
So if I draw that here, the mock version of the constuctor
has just been called, and basically that mock constructor
is identified here, "rc.RestClient" is the mock constructor,
"rc.RestClient."
And as we're gonna show in a moment, the mock object
you can verify that it has been called once.
When I created a RestClient object
you can go to the mock object
and say, "The mock constructor should have been called once"
because I've created a RestClient
and furthermore, a mock object, if you remember this
from one of the earlier lessons, when you have a mock object
the mock object has a property called mock.
Okay, so I can draw that here.
It has a property called mock.
And what that does, it gives you information,
a whole load of information actually
about how that mock object has been used.
So this mock property gives me a whole load
of metadata, a whole load of information
about how that constructor was invoked.
So if it's a constructor, for example it can say,
"The mock object, it has an instances property."
It has an instances property, which tracks all the objects,
all the RestClient objects that you've created
but I've only created one RestClient object here.
So this instance is zero should point to
the RestClient instance that I created.
I created the object here.
If I go to the mock function for the constructor,
go to the mock property, that's this object,
this mock object here has an instances property.
It tells me about all the instances that are being created.
Okay, so instances zero, it's an array, basically.
If I'd created multiple instances
then it would kind of look like this.
There would be multiple instances in an array like that,
but we've only created one instance
so it should just be element zero.
Element zero in my list of interfaces should be exist.
It shouldn't be undefined.
So just to summarize that then, the mock constructor
should have been called once.
The mock constructor, because it's a mock function,
it has a mock property and the mock property gives me
information about how that function has been called.
In the case of a constructor we can say,
"what instances have you accrued?"
Well, there should have been one instance, that instance,
so that instance should not be identified, it should exist.
So there's a lot of information available there.
I could run that test and it would work out.
Oh, don't forget to clear all mocks.
When you have mock objects, at the end of each test
you are to erase that mock history
that you've been accumulating like the list of instances,
clear that out at the end of each test so that
for the next test you start with a blank canvas again.
So I run that test and it works.
You can run it yourself and you'll find that it does work.
We're gonna be a little bit more ambitious now.
Instead of just creating a RestClient object directly
what I'm gonna do now is create it indirectly.
Have a look at this code, I create a ProductManager object.
Now, if you remember, when you create a ProductManager,
internally it also creates a RestClient.
Remember that?
Let me show you the code.
Here's my ProductManager.
When I create a ProductManager,
it creates a RestClient indirectly.
So think about what's going on here.
I've created a ProductManager.
Okay, so here's my ProductManager object.
It's a cloud shape, it has a pointer,
it basically called the RestClient constructor.
So basically this is the mock constructor, if you like,
for RestClient, it called the mock constructor
because I've mocked everything in the RestClient class.
When I call the constructor,
it calls the mock constructor for RestClient.
Okay, so we should be able to verify that the mock
constructor has been called by the ProductManager.
And we should be able to verify
that this mock constructor, which has a mock property
which I'll draw there, that's this in here,
that gives me kind of metadata about how many times
the mock object was called, and it should say,
"Well, these are the instances that have been created."
It has an instances property, same as just now instances.
And that basically points to all the RestClient objects
that I've created as an array.
Well, I've created one ProductManager
and that one ProductManager created one RestClient.
So that should indicate that the RestClient constructor,
one instance of RestClient has been created.
So that should just be element zero.
Okay, so just to reiterate then,
the ProductManager created the RestClient.
The RestClient mock says, "oh, I've been called."
And it gives me via mock property on the mock function,
the mock property will give me information
about all the instances that have been created,
that it has been created like so.
So to run that test, that's the name of the test.
If you look at the code,
"Call the RestClient constructor indirectly"
and if you run that test, like so, it works.
I've got one more example to show you.
This is a bit more tricky.
What I'm gonna do is I'm gonna call this method
on ProductManager, I'm going to remove a bunch of products
and I'm gonna pass in a commerce separated list of IDs.
When I call this function
it should call the delete function on my RestClient.
If I pass in three numbers here, it should call
the delete function three times on my RestClient
to delete each product with that ID.
So let's see how to test that.
When we call remove products, how can we verify,
I mean, I can see the code here, but how would I verify
from a testing point of view that if I remove products
with three numbers here it calls the delete function
three times on my RestClient interface?
You would want to be able to test that.
So this code here, this is quite involved,
so I'm gonna draw a picture
and then I'll kind of explain what's going on.
So, first of all, just looking at this line
that creates a ProductManager object,
which is still a cloud shape.
And as we've seen, it creates a RestClient
or to be more specific,
it calls the mock constructor for RestClient, think so.
And we can verify that the mock constructor
has been called once.
Oh, and by the way, then I call
the "remove products" method on ProductManager,
so I call the "remove product" method on there.
And what it should do is it should call delete
three times to delete product one, two and three.
It should call the delete function
three times on the RestClient.
So this code here verifies that it's done it properly.
I'm gonna explain what's going on.
So first of all, this is my mock constructor
and the mock constructor has a mock property,
we've seen that a few times now, it has a mock property.
Okay, and that gives me the mock object,
information about my RestClient.
And it has an instances field, as we know,
and that instances field,
it gives me back all the instances of RestClient.
I've only got one instance of RestClient,
so I'm gonna draw that here.
That's the instance of RestClient that was created
when I created the ProductManager.
It basically created the RestClient object here.
Okay, so this variable here is my RestClient object.
Now, this here basically represents your object,
your RestClient object
and the RestClient object had a bunch of methods.
For example, it had a delete method
and it had a get-all method and it had a get-one method.
And it had an update method, I think it was called.
Like that, so what you can do is you can actually say
"go to my RestClient object," which is this thing here.
And basically "give me a pointer to the delete method."
This here is the mock object
that represents the delete method for my bank account.
So effectively this is the mock
delete method on my RestClient.
And just like any mock functions, it has a mock property.
So this here has a mock property.
And that mock property gives me information
about how that delete method was called
upon this RestClient object.
So I can say, "go to this mock object
tell me information about your calls."
Basically this here tells me how many times
was the delete method called?
There should have been three calls.
My delete method has mock information.
It tells me how many times the mock function was called.
The delete function should have been called three times.
And you can get information about each call.
It has complete history of how methods
were invoked on the object.
We're basically saying for the RestClient,
which had a delete method, for the first call
the zero parameter should have been product 101.
For the next call, the zero parameter should have been 102.
For the next call to the delete method, for the second call,
effectively the third call,
then the first parameter should have been 103.
What I'm checking here is that when I called remove products
it called delete once, twice, three times on the RestClient
passing in the values, 101, 102, and 103.
So that's quite complicated, you might wanna run
that through again just to convince yourself.
I just summarize what I've just been describing.
First of all, create the ProductManager object
which creates a RestClient.
Verify that the RestClient constructor was invoked.
Call the method that we're trying to test
and then check how it worked.
From the constructor give me the mock information,
that'll give me back the RestClient object.
On that RestClient object, give me information
about the delete method on that object.
And then see if that delete method was called three times.
See if the first call passed in the first parameter of 101.
And if the second call, call number one
passed in the parameter of 102
and the final call passed in the parameter of 103.
So it takes a bit of effort but the advantage is
that the end of the day now you can verify that your code
interacts properly with the object that sits behind it.
So all we need to do now is run the test and relax.