Angular 2+ services can be tested in a couple of different ways, two most
prominent being isolated tests and using the Angular 2+
However, things get interesting when the service under test has dependencies (injected using Angular’s dependency injection).
In short, tests are considered isolated when a fresh instance of your service class is created before each test, like so:
Resulting in the following most basic test:
You can find more information on isolated service unit tests in the official Angular 2+ testing guide.
TestBed tests, you need to define / configure a testing module before each test:
The object we pass into the
configureTestingModule method follows the same structure as the
that we use when defining Angular 2+ modules within our application.
The simplest test we can write, if we are using the
TestBed would look something like this:
As you can see, the
TestBed tests require a bit more setup up front, but individual specs (
its) are not
any different. So why would you ever use the
TestBed for your service tests? There are advantages
to that approach if your service has dependencies.
As soon as you add even one dependency to your service, you need to also add it to your tests. In case of isolated tests, you will need to pass an instance of an injectable dependency class into the constructor of your service instantiation.
But this approach would only work if
MyServiceDependency does not have any dependencies itself.
If it does, you would obviously need to pass them in as well, and so on and so forth. This can
get out of hand really quickly.
With this approach it is also harder to mock and stub dependencies, which is something we would definitely prefer to do, as we only want to test a specific service, not the dependencies.
Let’s see if we can solve these problems in the
TestBed testing approach.
TestBed test is failing, with a
No Provider for MyServiceDependency! message, which indicates
that we are requesting an intance of
MyServiceDependency in our
MyService class, but we never
MyServiceDependency to any
providers of any known modules.
Let’s look at how we would add a dependency to our
TestBed tests in the most basic way.
That gets rid of the error, but we are still faced with the same problems as we did with isolated tests:
MyServiceDependencyhas dependencies - we would have to add them to the
- We are injecting an actual instance of
MyServiceDependencyand not a mock / stub.
The solution to both of these points would be to create a stub of
MyServiceDependency and inject it
instead of the real
MyServiceDependency. Something we can do very easily with the
Stubbing a dependency
First, we need to create a stub class of our
MyServiceDependency class that we will be injecting
instead of the real
Even if the class is blank, i.e. has no methods or properties - we should have no problems injecting it instead of the real service, which we would do in our testing module configuration.
Our test is now passing again.
As you write tests for your service, you will sooner or later encounter some code that uses the dependency, like calling a method or accessing a property on it, which will require you to add it to your stub.
For example, let’s say we are writing a test for the following
doSomething method on
Potential test for this method would be checking that
getSomeValue method is called on
However, because we are injecting a stub into our testing module, we need to make sure that when
doSomething will be called within our tests -
getSomeValue method is present on the stub. Simply
adding it to our stub will do.
Now, we can easily set up a spy on
getSomeValue and assert that it was actually called during the
doSomething, resulting in the following full spec file:
In case you ever forget to add a method to a stub, and then try to spy on that method, you will be greeted with an
Error: getSomeValue() method does not existerror.
Sample application demonstrating these concepts in a more realistic scenario can be found here