Test a Service
In this section, you will learn the basics of writing a service test with NgVacuum. We provide sample code so you can follow along.
tl;dr: jump directly to the recap for the full code example.
Sample project
Copy the code below if you would like to follow the examples in this section.
access-control.service.ts
auth.service.ts
Instantiate the service
The first thing you'll need when testing a service is... the service itself!
Here, we would like to test AccessControlService
. We instantiate it with getService
.
access-control.service.spec.ts
The next thing you'll need is... nothing!
This is the only boilerplate needed to bootstrap a service test with NgVacuum.
All the dependencies of AccessControlService
are automatically mocked and injected. You don't need to deal with this yourself.
Write a test case
We would like to test that checkAccess
redirects to the logout page when trying to access the lobby while unauthenticated. This corresponds to the first branch of the method, specifically:
We start off by calling the method with the appropriate parameter and asserting the expected result.
This is what we get when running this test:
This error message tells us that something tried to access a member called isAuthenticated
on the service AuthService
, but we did not specify what should happen in this situation.
This error comes from OmniMock, the type-safe mocking library used by NgVacuum to mock all service dependencies. This library is a safer alternative to standard jasmine mocks, as it was built for TypeScript and it is type safe out of the box.
note
In this situation, a typical jasmine mock will not throw an error and instead it will allow the method to be called and return undefined
. This default behavior most likely violates the type contract of the class and can lead to difficult bugs down the line, such as the infamous undefined is not a function
.
Specify mock behavior
So how do we specify what happens when AuthService.isAuthenticated
is called?
We first need to obtain a reference to the mock of AuthService
with getMock
. Then we use the mock DSL provided by OmniMock to specify the behavior.
Expect calls
At this point, our test is still not passing. It complains that no behavior was specified for <Router>.navigate
.
Again, we use the DSL to specify what happens when this method is called.
But in this case we don't just want to specify what to do when this method is called. We want to verify that this method was called with the appropriate arguments.
We us the quantifier .once()
to specify that we expect this method to be called exactly once.
Inceed, this test fails when we remove the call in access-control.service.ts
.
access-control.service.ts (snippet)
note
In OmniMock, you need to invoke verify()
on a mock to verify that the number of calls match the expectation.
NgVacuum takes care of this automatically for you; It calls verify()
on all mocks in an afterEach
function, catching any missing call before the test finishes.
Recap
We wrote a true unit test for a service in complete isolation, with practically no boilerplate. What's more, the whole code is type-safe and won't break unexpectedly during future refactoring of the main code.
The complete test is shown below for reference.
The combo NgVacuum + OmniMock offers an extremely powerful mocking machinery which is able to mock just about anything; from methods to members, accessors and more. We cover some common use cases later in mocking techniques.
But first, let's take a look at how to mock a component.