Testing Selectors page

Learn how to write unit tests for NgRx Selectors.

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

Overview

  1. Verify selectUserId() Selector returns userId.

  2. Verify selectUsername() Selector returns username.

  3. Verify selectToken() Selector returns token.

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

When testing Selectors, we will mock the Login State and verify that each Selector returns the expected result. Like testing the Reducer, we don’t need a TestBed since there shouldn’t be any dependencies when dealing with Selectors since they are pure functions.

Update login.selectors.spec.ts

We will walk through updating src/app/store/login/login.selectors.spec.ts to run tests for your Selectors.

Verify Feature State Selector

This test has already been written in a previous section where we were testing the Reducer (Testing Reducers):

src/app/store/login/login.selectors.spec.ts

// src/app/store/login/login.selectors.spec.ts

import * as fromLogin from './login.reducer';
import { selectLoginState } from './login.selectors';

describe('Login Selectors', () => {
  it('should select the feature state', () => {
    const result = selectLoginState({
      [fromLogin.loginFeatureKey]: {
        ...fromLogin.initialState,
        userId: 'some-user-id',
        username: 'some-username',
        token: 'some-token',
      },
    });

    expect(result).toEqual({
      userId: 'some-user-id',
      username: 'some-username',
      token: 'some-token',
    });
  });
});

The Feature State Selector is unique to all other Selectors for a Feature Store because this Selector is the source for all other Selectors for a Feature Store and is the only Selector generated using the createFeatureSelector() helper. Testing the Feature State Selector should be straight-forward since it should return all of the Login State.

Verify selectUserId Selector Returns userId

To properly test Selectors in a way that ensures that each Selector does not rely on mutations on the Login State, we will use beforeEach() to create a fresh Login State per test:

src/app/store/login/login.selectors.spec.ts

// src/app/store/login/login.selectors.spec.ts

import * as fromLogin from './login.reducer';
import { selectLoginState } from './login.selectors';

describe('Login Selectors', () => {
  let state: fromLogin.LoginPartialState;

  beforeEach(() => {
    state = {
      [fromLogin.loginFeatureKey]: {
        ...fromLogin.initialState,
        userId: 'some-user-id',
        username: 'some-username',
        token: 'some-token',
      },
    };
  });

  it('should select the feature state', () => {
    const result = selectLoginState(state);

    expect(result).toEqual({
      userId: 'some-user-id',
      username: 'some-username',
      token: 'some-token',
    });
  });
});

Now we can write our tests for our Selectors. The first Selector we will test is the selectUserId() Selector:

src/app/store/login/login.selectors.spec.ts

// src/app/store/login/login.selectors.spec.ts

import * as fromLogin from './login.reducer';
import {
  selectLoginState,
  selectUserId,
} from './login.selectors';

describe('Login Selectors', () => {
  let state: fromLogin.LoginPartialState;

  beforeEach(() => {
    state = {
      [fromLogin.loginFeatureKey]: {
        ...fromLogin.initialState,
        userId: 'some-user-id',
        username: 'some-username',
        token: 'some-token',
      },
    };
  });

  it('should select the feature state', () => {
    const result = selectLoginState(state);

    expect(result).toEqual({
      userId: 'some-user-id',
      username: 'some-username',
      token: 'some-token',
    });
  });

  describe('selectUserId', () => {
    it('should return userId from login state', () => {
      const result = selectUserId(state);

      expect(result).toBe('some-user-id');
    });
  });
});

Verify selectUsername() Selector Returns username

src/app/store/login/login.selectors.spec.ts

// src/app/store/login/login.selectors.spec.ts

import * as fromLogin from './login.reducer';
import {
  selectLoginState,
  selectUserId,
  selectUsername,
} from './login.selectors';


describe('Login Selectors', () => {
  let state: fromLogin.LoginPartialState;

  beforeEach(() => {
    state = {
      [fromLogin.loginFeatureKey]: {
        ...fromLogin.initialState,
        userId: 'some-user-id',
        username: 'some-username',
        token: 'some-token',
      },
    };
  });

  it('should select the feature state', () => {
    const result = selectLoginState(state);

    expect(result).toEqual({
      userId: 'some-user-id',
      username: 'some-username',
      token: 'some-token',
    });
  });

  describe('selectUserId', () => {
    it('should return userId from login state', () => {
      const result = selectUserId(state);

      expect(result).toBe('some-user-id');
    });
  });

  describe('selectUsername', () => {
    it('should return username from login state', () => {
      const result = selectUsername(state);

      expect(result).toBe('some-username');
    });
  });
});

Verify selectToken() Selector Returns token

src/app/store/login/login.selectors.spec.ts

// src/app/store/login/login.selectors.spec.ts

import * as fromLogin from './login.reducer';
import {
  selectLoginState,
  selectToken,
  selectUserId,
  selectUsername,
} from './login.selectors';

describe('Login Selectors', () => {
  let state: fromLogin.LoginPartialState;

  beforeEach(() => {
    state = {
      [fromLogin.loginFeatureKey]: {
        ...fromLogin.initialState,
        userId: 'some-user-id',
        username: 'some-username',
        token: 'some-token',
      },
    };
  });

  it('should select the feature state', () => {
    const result = selectLoginState(state);

    expect(result).toEqual({
      userId: 'some-user-id',
      username: 'some-username',
      token: 'some-token',
    });
  });

  describe('selectUserId', () => {
    it('should return userId from login state', () => {
      const result = selectUserId(state);

      expect(result).toBe('some-user-id');
    });
  });

  describe('selectUsername', () => {
    it('should return username from login state', () => {
      const result = selectUsername(state);

      expect(result).toBe('some-username');
    });
  });

  describe('selectToken', () => {
    it('should return token from login state', () => {
      const result = selectToken(state);

      expect(result).toBe('some-token');
    });
  });
});

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/store/login/login.selectors.spec.ts

// src/app/store/login/login.selectors.spec.ts

import * as fromLogin from './login.reducer';
import {
  selectLoginState,
  selectToken,
  selectUserId,
  selectUsername,
} from './login.selectors';

describe('Login Selectors', () => {
  let state: fromLogin.LoginPartialState;

  beforeEach(() => {
    state = {
      [fromLogin.loginFeatureKey]: {
        ...fromLogin.initialState,
        userId: 'some-user-id',
        username: 'some-username',
        token: 'some-token',
      },
    };
  });

  it('should select the feature state', () => {
    const result = selectLoginState(state);

    expect(result).toEqual({
      userId: 'some-user-id',
      username: 'some-username',
      token: 'some-token',
    });
  });

  describe('selectUserId', () => {
    it('should return userId from login state', () => {
      const result = selectUserId(state);

      expect(result).toBe('some-user-id');
    });
  });

  describe('selectUsername', () => {
    it('should return username from login state', () => {
      const result = selectUsername(state);

      expect(result).toBe('some-username');
    });
  });

  describe('selectToken', () => {
    it('should return token from login state', () => {
      const result = selectToken(state);

      expect(result).toBe('some-token');
    });
  });
});

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-selectors