- In this section
we're going to see how to test service classes.
We'll also see how to test components
that receive services injected into them.
Okay. Right.
The example is the same example as before,
frequent example three.
In the demo application,
I implemented a product service class
with a method that returned products.
The service classes in Angular are typically annotated
with injectable provided in root.
So quick reminder, what that means.
The provided in root means at the root level
in your application,
which will be the top level module.
Basically, the module will be responsible
for creating a single instance
of your product service class.
Any constructor that specifies that as an input,
like a component receiving a product service,
it will automatically receive that object
passed in as a dependency injection.
So in my example code,
if I go into the source folder, my application component
which is here,
it says in the constructor
please, will it give me a product service object.
It'll automatically receive the product service
injected into it.
So my application component here, my application component,
will automatically receive that product service as an input,
so we can then invoke methods upon it like that.
So what we're gonna do then,
is we're gonna see how to test this.
First of all, we'll see how to test
the product service class on its own.
And then, we'll see how to test a component that uses it.
How can we test this component that will hopefully
have received as an injection parameter?
One of those, how can we check that the dependency
injection has worked properly?
Well, let's see.
So let's see how to test the product service class.
Here we go, product.service.spec.ts
Let's get started.
Right, so couple of basic things.
Remember the test bed class.
When you say test bed configure testing module,
what you would normally do here
is you're given a bunch of components
that your test module needs to instantiate.
If you're testing a service class on its own,
then there aren't any components,
so we give it an empty context.
Effectively, our testing module
doesn't contain any components.
What we do instead is we inject into the test module,
we inject product service.
In the full blown application,
when the application starts,
the module would've created this service
for us automatically.
That's what it does.
But if you just set up a test environment,
and you're just testing your product service,
there isn't anything here yet that would've told it
to create a product service object,
so you have to do it yourself.
You say, test bed, inject.
So effectively, the test module that gets set up for us
that would normally contain components.
What we've just done there is we've told Angular,
or the Angular test environment,
basically create an instance of product service.
So let's put it there.
And basically put it into the testing module
effectively so that it can then be injected,
potentially, if necessary.
It gives us back a pointer to the object it creates.
We give it the class name.
It creates an instance.
It gives us back a reference to that instance.
So this variable here is a pointer
to the singleton product service object that has
been created because of the inject function.
Okay. So what we can do now is we can invoke methods
on that service.
At the moment, I'm just checking,
does that service actually exist?
Has it been created?
Yes.
If I wanted to go further,
I could have invoked the get products method on it
to see does it return the correct bunch of products?
Okay? But this is a simple way
of getting the test bed to create an instance
of your service, get a handle to it,
and then you can test that it's being created
and that it works properly.
This is my product service spec.
Okay. So injected a service into the module
got back a reference.
We can then test my product service spec.
Remember, when you run in tests with Karma,
you can say which tests you want to run in particular.
I'm just running the product service test at the moment.
And well, as you can imagine, it works fine.
So we've just seen, basically,
we had like a simplified setup.
We had a test module.
We injected, or got created, a product service object.
Okay. Which we could then test against.
What we're gonna do now
is we're gonna test components
that receive that injected into it.
We're gonna see how we can test these components
that receive an injected service.
So here is a component that uses a service.
It's my application component.
I'm gonna give you a quick reminder
of the application component.
So the application component receives a product service.
Okay?
It invokes the get product method to get back
a bunch of products, puts it into a collection here,
and then it renders that collection as a list of products.
We've seen how that works many times now.
Okay. So I've actually got two different tests,
just to show different techniques.
First of all, app.component1.spec.ts.
Right, so what we do here is,
this is going back to a more familiar arrangement
where we go to our test bed
and we say, please configure a testing module.
So this is going to be our test module.
This one will contain the components that we need.
So our test module will contain app component.
Okay. So let me put app component class.
Okay. That's good.
So far, so good.
Oh, and then we compile components.
Well, we know what that does.
It basically generates JavaScript for the HTML
for my application component.
Anyway.
So far, my testing module knows
about the application component.
All good?
Okay. What we then do, is we say,
okay then can you go off and create an instance
of that please?
I'm sent to the test bed.
Will you please create an instance
of my application component?
Now at that point,
it realizes that my application component has
to have a service injected into it.
Okay.
Like that.
My test module will automatically realize
that, well, because of the way my product service
is implemented, my product service says,
I am injectable at the root level.
Okay. So it knows that this service is meant to be provided
at the module level.
So as soon as my component says, give me a product service.
It'll kind of realize that that product service has to
be provided at the root level.
In other words, it'll create one of these product services
and add it to the module automatically.
So the module will kind of automatically realize
that it's meant to create a singleton instance
of product service.
And once it's done that, it'll then pass that in
to my application component.
My application component will automatically
receive injected into it
that product service as a singleton.
Okay. So here's my application component.
My application component will receive that product service
as a parameter, which is great.
We actually do get a real instance to the singleton,
the product service, the real product service object.
Okay, great.
So let's run the test.
When I run the test, it actually all works successfully.
Okay. So what just happened there, is in my test
it got the application component,
or rather it got a fixture that wraps it up.
It got the component represented by the fixture,
and it said, did that component get created successfully
with a reference to the product service as well?
Right. So that's good.
Now then what we've just done is integration testing
because in my test module,
my test module basically injected the real product service
into my application component.
So when my application component started up,
it basically called get products in the constructor
for application component, it called the get products method
on the real product service,
which might have called a rest service to get the data back.
So that's integration testing.
My component received the real product service
injected into it.
Okay. So, but what if you wanted to do unit testing?
With unit testing,
you meant to just test one class at a time.
So a better arrangement for unit testing
would be not to inject the real product service,
but maybe to create some kind of mock version of it.
Okay. So I can arrange for my test module
to inject a mock version of product service.
I'll call it PS dash.
We can create a mock implementation
instead of the real product service.
And then that is what's gonna be injected
into my application component.
So when it calls get products
it calls a dummy version of the method that I can provide
that doesn't actually call a rest service.
And, you know,
instead maybe return some hard coded data instead.
So effectively, the product service can be mocked,
and I can just unit test my component in isolation.
That seems like a good thing to do.
So this is called, this mock in Angular terminology,
this mock implementation of the service
in Angular terminology is called a spy.
Okay. It's basically a replacement
for the real product service
with a simplified implementation.
And this is how you do it.
I've got app.component2.spec
where I'm going to be using a mock product spec
service instead of the real one.
Have a look at the first statement there.
Jasmine creates spy object.
You give it two parameters.
You give it the name of the class
that you're meant to be kind of mocking
and the method or the array
of methods that you want to replace.
So effectively, this will give you back a spy object.
And that spy object basically is a replacement
for product service.
Anyone that asks for product service.
Well, this is the class that we're mocking, basically.
And in particular,
we're mocking the get products method on there.
You can mock multiple methods if you like,
but I'm just mocking the get products method.
Okay. You could mock other methods
on product service as well if you wanted to.
Well, I don't particularly.
It's just that one.
So this here is my spy object.
Okay. It's like a replacement for the product service.
What you then do, is you basically tell the testing module,
if anyone asks for a product service, use this one instead.
So when I configure my testing module,
I give it the components of interest and providers.
Now, if you haven't seen this before,
when you have a model,
you can basically tell it how to inject dependencies.
So you can give it, if there were like five
different classes that you might want to inject,
you would have an array of five different bits
of information here, how to inject each dependency.
So this is just one dependency.
It basically says, if anybody asks for a product service
to be injected, use this value instead.
If any component asks for a product service to be injected,
inject the spy object.
Well, that's quite handy because
if you remember, in my application component,
my application component does actually say,
please will you give me a product service from somewhere?
I don't care where it comes from.
You just give it to me.
And what this does, it said,
if any component asks for a product service,
give them this spy object instead.
What that means is,
when an application component is created
and when it asks the testing module,
basically, please really give me a product service
injected into my constructor,
it'll get the spy object.
The spy object is what's going to be injected
into the application component.
So then, whenever the application component calls a method
on a product service, it'll be called a method
on the spy and we can mock that implementation.
Right, so we've already set up then.
We have a spy that basically points to
like a mock version
of a product service class and product service.
And in particular, the method that we were kind
of replacing was called get products.
Okay. Get products, like that.
Okay. Right.
So this statement here is going to be important.
Before we get to that, I need to hook it up.
What's gonna happen in a moment,
is that my application component is gonna basically
call product service, get products on my spy.
That's not a real method.
It doesn't actually exist.
So we have to basically kind of give a stub implementation.
And that's what this does.
It says with this spy here,
if somebody calls get products,
then return this, the mock data.
Okay. So instead of calling the real get products method
on the real service, basically we turn
this dummy method instead.
In other frameworks,
this is called setting expectations.
Okay.
So I've got some dummy products that will be returned here.
Okay, bear that in mind.
Let's look at the test harness when we run it
it all works. Good.
Let's see the code in a bit more detail, then,
in app.component.spec2.
So we've set up a spy
on product service for the get products method.
We've configured the testing module
so that if anybody asks to have one of those injected,
it'll inject the spy
In my test, I create some dummy data,
and I say, upon my spy object, in other words,
upon this spy object here, my pretend product service,
if anybody calls to get products method,
return them these dummy products instead.
Okay. Now here, I create the application component.
The application component, remember,
says, please will you give me a product service?
Right.
And it said earlier, didn't it?
If anyone asks for a product service
give them the spy object instead.
So the application component will basically
get the spy object.
When it calls in its constructor,
when it calls get products,
it's gonna basically call this method
and that will return the dummy products,
product one and product two.
Those are the products that will actually be assigned
into my component.
So do some data binding,
and then see what was generated.
In my application component, basically,
give me the rendered component structure,
find how many accounts there were of a product.
Remember, for each product it rendered an app product.
For each product it rendered an app product.
Okay. So what we should see then
is that there should have been two of them.
The number of products displayed should have been two
because when my application component got the products,
it got the mock version, not the real version.
So this is useful for unit testing.
When your component has dependencies injected into it,
you can arrange for mock versions
to be injected instead of real versions.
Then when your component calls a method on the service,
you can basically say how that method should be mocked,
so that you are in control of the data
in your test, so that you can predict exactly what kind
of results you're gonna get back.
So quite complicated.
Quite a lot of steps involved there,
but very much the way that you would do unit testing
when you have components with injected services.