Testing RxJS Service with a Subject Component Interaction

The service with a subject pattern is a good entry point into the RxJS world of observables in Angular. When you are using RxJS, writing traditional tests can be cumbersome. There are great tools, such as jasmine-marbles, that help make these kinds of tests a lot easier. A great way to get started with jasmine-marbles is testing the service with a subject when the service only performs HTTP calls. However, when the service also accepts updates from components, writing these tests can be more difficult as you have to bridge the gap between the timing of function calls and observables.

The Service under Test

To illustrate a starting point for how to write these kinds of tests, consider a service that supports a component that allows the user to pick the sauce they would like to have on their burger 🍔. You might come up with a sauce service to keep track of which sauce was picked, such as the one shown below.
sauce.service.ts

// sauce.service.ts
import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class SauceService {
  private mSauce$ = new BehaviorSubject<string>('initial sauce');

  constructor() {}

  sauce$(): Observable<string> {
    return this.mSauce$;
  }

  setSauce(sauce: string) {
    this.mSauce$.next(sauce);
  }
}

Testing the Service

There are two things you may be interested in testing. The first is that the initial sauce is set correctly and the second is that, on calling setSauce, that the new sauce is emitted by sauce$. Both of these can be tested using jasmine-marbles. Without jasmine-marbles, you would have to subscribe to sauce$, check that the correct sauces are emitted and make use of the done function. With jasmine-marbles this can be simplified.

To see how, you need to understand that the jasmine-marbles cold function simply creates an observable. Just like any other observable, you can subscribe to it. So if you define an observable that emits the sauces that you want to call setSauce with using cold, subscribe to that observable and call setSauce with the sauces that you receive, you have bridged the gap between function calls an observables. The marble syntax allows you to specify the timing of the setSauce calls. The tests could like like what is shown below.
sauce.service.spec.ts

// sauce.service.spec.ts
import { cold } from 'jasmine-marbles';

import { SauceService } from './sauce.service';


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

  beforeEach(() => {
    service = new SauceService();
  });

  describe('construction', () => {
    it('should emit initial sauce', () => {
      // Setting up expected observable
      const expectedMarbles = 'a';
      const expected = cold(expectedMarbles, {a: 'initial sauce'});

      // Checking sauce$
      expect(service.sauce$()).toBeObservable(expected);
    });
  });

  describe('setSauce', () => {
    it('should emit new sauce', () => {
      // Defining marbles
      const setSauceCallMarbles = '-a';
      const expectedMarbles =     'bc';

      // Setting up setSauce call
      cold(setSauceCallMarbles, {a: 'new sauce'})
        .subscribe(sauce => service.setSauce(sauce));

      // Checking sauce$
      const expected = cold(expectedMarbles, {b: 'initial sauce', c: 'new sauce'});
      expect(service.sauce$()).toBeObservable(expected);
    });
  });
});

The test on lines 15-22 checks that the initial sauce is emitted after construction. The interesting test is on lines 26-39. On line 28 the timing of the setSauce calls is defined. In this case, it is setup so that the first setSauce call occurs one frame after the construction is complete. On lines 32-33 the input to the setSauce call is defined and the plumbing to perform the setSauce calls is specified. On lines 36-37 the standard work is being done to check the values emitted by sauce$.

This technique is more concise and easy to understand than messing around with observables, subscriptions and the done function. There are still things that could be better. For example, it would be great if jasmine-marbles could come up with a standardised way of performing this technique (maybe they already have and I just don’t know about it). You may not feel like using this technique all the time, depending on the complexity of the logic and its dependence on timing. However, knowing about it and how it helps you can be valuable when you encounter the need for finer control over timing of function calls in tests.

Testing Reactive Components in Angular

The Service with a Subject pattern in Angular is a good way to get started with reactive programming (ie. making use of RxJS) in Angular. The following article describes how to get started with the pattern: Getting Started with Service with a Subject in Angular.

That pattern leads to writing reactive components for which standard Angular component testing would lead to more complicated tests. With some changes to the standard Angular component testing methodologies, the tests can be simplified significantly.

Reactive Component under Test

For reference, the following is the typescript and HTML template for the component.
name.component.ts

// name.component.ts
import { Component, OnInit } from '@angular/core';

import { NameService } from './name.service';

@Component({
  selector: 'app-name',
  templateUrl: './name.component.html',
  styleUrls: ['./name.component.css']
})
export class NameComponent implements OnInit {
  constructor(public nameService: NameService) { }

  ngOnInit() {
    this.nameService.loadName();
  }

}

name.component.html

<!-- name.component.html -->
<ng-container *ngIf="nameService.name$() | async as name">
  {{ name }}
</ng-container>

Since the component depends on a reactive service, for reference, the following is sample code for the reactive service. For testing the reactive service see Testing Reactive Service with a Subject in Angular.
name.service.ts

// name.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable, of } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NameService {
  private mName$ = new BehaviorSubject<string>(null);

  name$(): Observable<string> {
    return this.mName$;
  }

  constructor(private httpClient: HttpClient) { }

  loadName() {
    this.httpClient.get<string>('name URL')
      .subscribe(
        name => this.mName$.next(name)
      );
  }
}

To figure out what needs to be tested, let’s think about what could go wrong. There are 3 main things that are likely to go wrong. The first is that the component doesn’t even get created due to some syntactical or import error. The second is that the loadName function on the NameService doesn’t get called during initialisation. The third is that, once the NameService name$ function emits a name, it doesn’t get displayed in the component.

Reactive Component Tests

The following is the test code that checks the 3 things that are likely to go wrong described earlier.
name.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of } from 'rxjs';

import { NameService } from './name.service';
import { NameComponent } from './name.component';

describe('NameComponent', () => {
  let nameServiceSpy: jasmine.SpyObj<NameService>;
  let component: NameComponent;
  let fixture: ComponentFixture<NameComponent>;

  beforeEach(() => {
    nameServiceSpy = jasmine.createSpyObj('NameService', ['loadName', 'name$']);

    TestBed.configureTestingModule({
      declarations: [ NameComponent ],
      providers: [ { provide: NameService, useValue: nameServiceSpy } ]
    });

    fixture = TestBed.createComponent(NameComponent);
    component = fixture.componentInstance;
  });

  it('should create', () => {
    // Checking component creation
    expect(component).toBeTruthy();
  });

  it('should call loadName on NameService', () => {
    fixture.detectChanges();

    // Checking loadName call
    expect(nameServiceSpy.loadName).toHaveBeenCalledWith();
  });

  it('should display NameService name', () => {
    // Setting name in service
    nameServiceSpy.name$.and.returnValue(of('the name'));

    fixture.detectChanges();

    // Checking displayed name
    expect(fixture.debugElement.nativeElement.innerText).toEqual('the name');
  });
});

Test Setup

The setup common to all tests is on lines 8-21 which define the component under test and other supporting variables needed for he tests. Lines 13-21 define the standard TestBed initialisation and injection logic. The three test scenarios are defined on lines 24-27 (checking the component can be created), 29-34 (checking that loadName is called on NameService) and 36-44 (checking that the name emitted on name$ is displayed).

Component Setup

The test on lines 24-27 checks that the component can be setup. This is a simple test but is a very useful step for verifying that all the component plumbing is correct and that the test setup has actually worked. If this test fails it is likely because an injection is missing or because the TestBed was not setup correctly.

loadName Call

The test on lines 29-34 checks that the loadName function is called on the NameService. For the ngOnInit function to be triggered, change detection is run on line 30. Line 33 checks that loadName has been called on the NameService.

Name Display

The test on lines 36-44 checks that, when name$ on NameService emits a new name, that name is displayed in the component. Line 38 triggers emitting a new name on name$. Line 40 triggers change detection so that the name is displayed. Line 43 checks that the name is displayed in the component.

These tests are the base on which tests can be built if the logic in the component becomes more complicated. Examples of additional complications are having to pass arguments to the service loadName call, displaying data from multiple services or subjects and a more complicated template that includes conditional displaying of data.

Getting Started with Service with a Subject in Angular

A powerful technique in Angular is the built in integration with RxJS. Making use of reactive programming (ie. RxJS) can remove a lot of code from your Angular application. This is an indictor that reactive programming takes care of a lot of the logic you would otherwise have to come up with and test yourself.

There are simple steps you can take to get started with writing reactive Angular applications. You can then use the understanding and confidence you get from the simple steps to start using reactive programming in other, more complicated cases.

Simple Service with a Subject

The following code is a simple Angular service with a subject.
name.service.ts

// name.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { BehaviorSubject, Observable } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class NameService {
  private mName$ = new BehaviorSubject<string>(null);

  name$(): Observable<string> {
    return this.mName$;
  }

  constructor(private httpClient: HttpClient) { }

  loadName() {
    this.httpClient.get<string>('name URL')
      .subscribe(name => this.mName$.next(name));
  }
}

There are 3 pieces to the subject that come together to trigger getting the name from the API and passing on the retrieved name.

API Call

The entry point for a user of the service is the loadName function on line 18. It triggers the HTTP call to the API and passes on the retrieved name to mName$ in the subscribe call on line 20.

Storing the Retrieved Name

The mName$ member variable on line 10 of the service is used as private intermediate storage of the name retrieved from the API in the service. mName$ is tightly linked to the name$ function which is used to control access to the intermediate storage.

Passing on the Retrieved Name

The name$ function of the service is used to pass on the name retrieved from the API on line 12. Hiding the private intermediate storage mName$ behind the name$ function allows control over how the name is distributed throughout the Angular application.

The next step is to show the name in a component.

Reactive Component

Once the name has been retrieved from the API, the next step is to show it in a component.

Component Typescript Code

The following is the typescript code for the component.
name.component.ts

// name.component.ts
import { Component, OnInit } from '@angular/core';

import { NameService } from './name.service';

@Component({
  selector: 'app-name',
  templateUrl: './name.component.html',
  styleUrls: ['./name.component.css']
})
export class NameComponent implements OnInit {
  constructor(public nameService: NameService) { }

  ngOnInit() {
    this.nameService.loadName();
  }

}

There is hardly any code in the component that is not part of the required Angular component code. The only line that has been added is line 15 which instructs the service to load the name along with importing the service on line 4 and injecting it into the component on line 12.

Component Template

The following is the HTML template associated with the component.
name.component.html

<!-- name.component.html -->
<ng-container *ngIf="nameService.name$() | async as name">
  {{ name }}
</ng-container>

In the template the name$ function of the service is passed to the async pipe on line 2. The values emitted by the name$ observable are renamed to the name variable and then displayed on line 3.

That concludes the simple example for making use of a subject with a service. There are some additions to consider depending on the use case. For example, you may want to introduce error handling in the subject. You may also want to write tests for the service and the component.