Testing Selector Use page

Learn how to write unit tests for a Component making use of NgRx Selectors.

Quick Start: You can checkout this branch to get your codebase ready to work on this section.

Overview

  1. Verify DashboardComponent’s username$ member extracts username from Login State.

  2. Verify AuthenticationGuard’s userId$ member extracts userId from Login State.

Running Tests

To run unit tests in your project, you can either use the test npm script, or the ng test command:

npm run test
# or
ng test --watch

The --watch switch will rerun your tests whenever a code file changes. You can skip it to just run all tests once.

Description

In this section, we will write unit tests involving Selectors. Whenever we use Selectors in our Components, Services, etc. we can use provideMockStore() to mock the behavior of our Selectors.

Update dashboard.component.spec.ts

We will walk through updating src/app/dashboard/dashboard.component.spec.ts to run tests for your Sectors.

Updating our TestBed

When testing our use of Selectors, we can specify a configuration to our MockStore. It is possible to pass an initialState option which will influence our Selectors:

src/app/dashboard/dashboard.component.spec.ts

// src/app/dashboard/dashboard.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from '../store/login/login.actions';
import * as fromLogin from '../store/login/login.reducer';

describe('DashboardComponent', () => {
  let component: DashboardComponent;
  let fixture: ComponentFixture<DashboardComponent>;
  let store: MockStore<fromLogin.LoginPartialState>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [DashboardComponent],
      providers: [
        MockStore,
        provideMockStore({
          initialState: {
            [fromLogin.loginFeatureKey]: {
              userId: 'some-user-id',
              username: 'some-username',
              token: 'some-token',
            },
          },
        }),
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(DashboardComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    store = TestBed.inject(MockStore);
  });

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

  describe('logout()', () => {
    it('should dispatch logout action', () => {
      const spy = spyOn(store, 'dispatch');

      component.logout();

      expect(spy).toHaveBeenCalledOnceWith(LoginActions.logout());
    });
  });

  describe('username$', () => {
    it('should get username from login state', () => {
      // TODO: Verify username comes from login state
    });
  });

  describe('userId$', () => {
    it('should get userId from login state', () => {
      // TODO: Verify userId comes from login state
    });
  });
});

When our updated configuration, we can now update the remaining TODOs to verify the behavior of DashboardComponent.username$ and DashboardComponent.userId$:

src/app/dashboard/dashboard.component.spec.ts

// src/app/dashboard/dashboard.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from '../store/login/login.actions';
import * as fromLogin from '../store/login/login.reducer';

describe('DashboardComponent', () => {
  let component: DashboardComponent;
  let fixture: ComponentFixture<DashboardComponent>;
  let store: MockStore<fromLogin.LoginPartialState>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [DashboardComponent],
      providers: [
        MockStore,
        provideMockStore({
          initialState: {
            [fromLogin.loginFeatureKey]: {
              userId: 'some-user-id',
              username: 'some-username',
              token: 'some-token',
            },
          },
        }),
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(DashboardComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    store = TestBed.inject(MockStore);
  });

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

  describe('logout()', () => {
    it('should dispatch logout action', () => {
      const spy = spyOn(store, 'dispatch');

      component.logout();

      expect(spy).toHaveBeenCalledOnceWith(LoginActions.logout());
    });
  });

  describe('username$', () => {
    it('should get username from login state', done => {
      component.username$.subscribe(username => {
        expect(username).toBe('some-username');
        done();
      });
    });
  });

  describe('userId$', () => {
    it('should get userId from login state', done => {
      component.userId$.subscribe(userId => {
        expect(userId).toBe('some-user-id');
        done();
      });
    });
  });
});

Final Result

At the end of this section, the following spec file(s) should be updated. After each spec file has been updated and all the tests have passed, this means that all the previous sections have been completed successfully:

src/app/dashboard/dashboard.component.spec.ts

// src/app/dashboard/dashboard.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { DashboardComponent } from './dashboard.component';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from '../store/login/login.actions';
import * as fromLogin from '../store/login/login.reducer';

describe('DashboardComponent', () => {
  let component: DashboardComponent;
  let fixture: ComponentFixture<DashboardComponent>;
  let store: MockStore<fromLogin.LoginPartialState>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [DashboardComponent],
      providers: [
        MockStore,
        provideMockStore({
          initialState: {
            [fromLogin.loginFeatureKey]: {
              userId: 'some-user-id',
              username: 'some-username',
              token: 'some-token',
            },
          },
        }),
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(DashboardComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
    store = TestBed.inject(MockStore);
  });

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

  describe('logout()', () => {
    it('should dispatch logout action', () => {
      const spy = spyOn(store, 'dispatch');

      component.logout();

      expect(spy).toHaveBeenCalledOnceWith(LoginActions.logout());
    });
  });

  describe('username$', () => {
    it('should get username from login state', done => {
      component.username$.subscribe(username => {
        expect(username).toBe('some-username');
        done();
      });
    });
  });

  describe('userId$', () => {
    it('should get userId from login state', done => {
      component.userId$.subscribe(userId => {
        expect(userId).toBe('some-user-id');
        done();
      });
    });
  });
});

Wrap-up: By the end of this section, your code should match this branch. You can also compare the code changes for our solution to this section on GitHub or you can use the following command in your terminal:

git diff origin/test-used-selectors