Unit Testing Angular - Services
Tags: angular, testing, typescript, javascript
Published: 2020-03-29 15:00:48

Following on from my previous post where I introduced unit testing Angular Components, this post will give a quick overview on practices I employ to unit test my services. In this post we will cover:

- Setting up a Service Test 💪
- Testing methods in the Service 🛠
- Mockng dependencies with Jasmine Spys 🔎

We will write some basic logic to handle a customer placing an order to illustrate the testing of the services involved.

## Let's Get Started 🔥

Before we get into the fun part, we need to scaffold up a new Angular Project so that we can write and run our tests. Open your favourite Terminal or Shell at a new directory.

_If you haven't already, I'd recommend installing the Angular CLI globally, it will be used frequently in this article: `npm install -g @angular/cli`_

Now that we are in an empty directory, the first thing we'll want to do is set up an Angular project:

`ng new test-demo`

_When it asks if you'd like to setup Angular Routing, type N, and when it asks which stylesheet format you would like to use, select any, it won't matter for this post._

Once the command has completed, you will need to navigate into the new project directory:

`cd test-demo`

We now have our basic app scaffold provided for us by Angular. Now we are going to want to set up some of the code that we'll be testing.

At this point, it's time to open your favourite Text Editor or IDE (_I highly recommend [VS Code](https://code.visualstudio.com)_).
Inside the `src/app` directory, create a new directory and name it `models`. We will create three files in here:

`user.ts`

```ts
export interface User {
  id: string;
  name: string;
}
```

`product.ts`

```ts
export interface Product {
  id: string;
  name: string;
  cost: number;
}
```

`order.ts`

```ts
import { User } from './user';
import { Product } from './product';

export interface Order {
  id: string;
  user: User;
  product: Product;
}
```

Once this is complete, we will use the Angular ClI to scaffold out two services:

`ng g service services/user`
and
`ng g service services/order`

These services will contain the logic that we will be testing. The Angular CLI will create these two files for us as well as some boilerplate testing code for each of the services. 💪

If we open `order.service.spec.ts` as an example we will see the following:

```ts
import { TestBed } from '@angular/core/testing';

import { OrderService } from './order.service';

describe('OrderService', () => {
  let service: OrderService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(OrderService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});
```

Let's break that down a litte 🔨:

`describe('OrderService', () => { ... })`
sets up the Test Suite for the Order Service.

`let service: OrderService`
declares a Test Suite-scoped variable where we will store a reference to our Service.

```ts
beforeEach(() => {
  TestBed.configureTestingModule({});
  service = TestBed.inject(OrderService);
});
```

This tells the test runner (Karma) to run this code before every test in the Test Suite. It is using Angular's `TestBed` to create the testing environment and finally it is injecting the `OrderService` and placing a reference to it in the `service` variable defined earlier.
_Note: if using Angular < v9 you may notice `TestBed.get(OrderService)` rather than `TestBed.inject(OrderService)`. They are essentially doing the same thing._

```ts
it('should be created', () => {
  expect(service).toBeTruthy();
});
```

the `it()` function creates a new test with the title `should be created`. This test is expecting the `service` varibale to truthy, in otherwords, it should have been instantiated correctly by the Angular TestBed. I like to think of this as the sanity check to ensure we have set up our Service correctly.

## Service Logic Time 💡

Now that we have a basic understanding of what our Service Test file looks like, lets create some quick logic in our `user.service.ts` and `order.service.ts` file for us to test.

In `user.service.ts` let's place the following code, which will store the active user in our app:

```ts
import { Injectable } from '@angular/core';

import { User } from '../models/user';

@Injectable({
  providedIn: 'root'
})
export class UserService {
  // Store the active user state
  private activeUser: User;

  constructor() {}

  getActiveUser() {
    // We'll return the active user or undefined if no active user
    // The cast to Readonly<User> here is used to maintain immutability
    // in our stored state
    return this.activeUser as Readonly<User>;
  }

  setActiveUser(user: User) {
    this.activeUser = user;
  }
}
```

And in `order.service.ts` let's create simple method to create an order:

```ts
import { Injectable } from '@angular/core';

import { Order } from './../models/order';
import { Product } from '../models/product';
import { UserService } from './user.service';

@Injectable({
  providedIn: 'root'
})
export class OrderService {
  constructor(private readonly userService: UserService) {}

  createOrder(product: Product): Order {
    return {
      id: Date.now().toString(),
      user: this.userService.getActiveUser(),
      product
    };
  }
}
```

Awesome! We now have a nice little piece of logic that we can write some unit tests for.

## Testing Time 🚀

Now for the fun part 💪 Let's get writing these unit tests. We'll start with `UserService` as it is a more straightforward class with no dependencies.

Open `user.service.spec.ts` and below the first test, we'll create a new test:

```ts
it('should set the active user correctly', () => {
  // Arrange
  const user: User = {
    id: 'test',
    name: 'test'
  };

  // Act
  service.setActiveUser(user);

  // Assert
  expect(service['activeUser'].id).toEqual('test');
  expect(service['activeUser'].name).toEqual('test');
});
```

In this test, we are testing that the user is set active correctly. So we do three things:

- Create a test user
- Call the `setActiveUser` method with our test user
- Assert that the private `activeUser` property has been set with our test user.

  _Note: It is generally bad practice to access properties via string literals, however, in this testing scenario, we want to ensure correctness. We could have called the `getActiveUser` method instead of accessing the private property directly, however, we can't say for certain if `getActiveUser` works correctly at this point._

Next we want to test that our `getActiveUser()` method is working as expected, so let's write a new test:

```ts
it('should get the active user correctly', () => {
  // Arrange
  service['activeUser'] = {
    id: 'test',
    name: 'test'
  };

  // Act
  const user = service.getActiveUser();

  // Assert
  expect(user.id).toEqual('test');
  expect(user.name).toEqual('test');
});
```

Again, we're doing three things here:

- Setting the current active user on the service
- Calling the `getActiveUser` method and storing the result in a `user` variable
- Asserting that the `user` returned is the active user we originally arranged

These tests are pretty straightforward, and if we run `ng test` now we should see Karma reporting `TOTAL: 7 SUCCESS`

Awesome!! 🔥🔥

## Testing with Mocks

Let's move onto a more complex test which involves having to mock out a dependency.

The first thing we are going to want to do is to mock out the call to the `UserService`. We are only testing that `OrderService` works correctly, and therefore, we don't want any ill-formed code in `UserService` to break our tests in `OrderService`.

To do this, just below the `let service: OrderService;` line, add the following:

```ts
const userServiceSpy = jasmine.createSpyObj<UserService>('UserService', ['getActiveUser']);
```

And then inside the `beforeEach` we want to change our `TestBed.configureTestingModule` to match the following:

```ts
TestBed.configureTestingModule({
  providers: [
    {
      provide: UserService,
      useValue: userServiceSpy
    }
  ]
});
```

Let me explain what's going on here. Jasmine creates an object identical to the UserService object, and the we override the Service being injected into the Testing Module with the spy object Jasmine created. (_This is a technique centered around the [Dependency Inversion principle](https://en.wikipedia.org/wiki/Dependency_inversion_principle)_).

Now we are able to change what is returned when our code calls `userService.getActiveUser()` to allow us to perform multiple test cases. We will see that in action now when we write our test for the `OrderService`:

```ts
it('should create an order correctly', () => {
  // Arrange
  const product: Product = {
    id: 'product',
    name: 'product',
    cost: 100
  };

  userServiceSpy.getActiveUser.and.returnValue({ id: 'test', name: 'test' });

  // Act
  const order = service.createOrder(product);

  // Assert
  expect(order.product.id).toEqual('product');
  expect(order.user.id).toEqual('test');
  expect(userServiceSpy.getActiveUser).toHaveBeenCalled();
});
```

We are doing 5 things in this test:

- Creating the product that the user will order
- Mocking out the response to the `getActiveUser` call to allow us to set up a test user
- Calling the `createOrder` method with our test product
- Asserting that the order was indeed created correctly
- Asserting that the `getActiveUser` method on `UserService` was called

And now, if we run `ng test` again, we will see 8 tests passing!

With just these few techniques, you can go on to write some pretty solid unit tests for your services! 🤓

Your team, and your future self, will thank you for well tested services!

---

This is a short brief non-comprehensive introduction into Unit Testing Services with Angular with Jasmine and Karma.

If you have any questions, feel free to ask below or reach out to me on Twitter: [@FerryColum](https://twitter.com/FerryColum).