---
title: Bitovi Academy - Testing Actions
description: Learn how to write unit tests for NgRx Actions.
---

- ![AI implementation](https://www.bitovi.com/hubfs/AIConsultingIcon.svg)
  
  [AI implementation](https://www.bitovi.com/services/ai-consulting?hsLang=en-us)
- ![Systems engineering](https://www.bitovi.com/hubfs/icon%20-%20backend.svg)
  
  [Systems engineering](https://www.bitovi.com/services/systems-engineering-consulting?hsLang=en-us)
- ![Project management](https://www.bitovi.com/hubfs/icon%20-%20PM.svg)
  
  [Project management](https://www.bitovi.com/services/agile-project-management-consulting?hsLang=en-us)
- ![Product design](https://www.bitovi.com/hubfs/icon%20-%20design.svg)
  
  [Product design](https://www.bitovi.com/services/product-design-consulting?hsLang=en-us)
- ![Frontend development](https://www.bitovi.com/hubfs/icon%20-%20frontend.svg)
  
  [Frontend development](https://www.bitovi.com/services/frontend-development-consulting?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/digital-consulting-services?hsLang=en-us)

We're Experts in...

- [JavaScript](https://www.bitovi.com/services/frontend/javascript-consulting?hsLang=en-us)
- [AI training](https://www.bitovi.com/ai-training-for-software-engineers?hsLang=en-us)
- [Angular](https://www.bitovi.com/services/frontend/angular-consulting?hsLang=en-us)
- [Design Systems](https://www.bitovi.com/services/axure-figma-migration?hsLang=en-us)
- [React](https://www.bitovi.com/services/frontend/react-consulting?hsLang=en-us)
- [Temporal](https://www.bitovi.com/services/backend/temporal-consulting?hsLang=en-us)
- [React Native](https://www.bitovi.com/services/frontend/react-consulting/react-native?hsLang=en-us)
- [Node.js](https://www.bitovi.com/services/backend/nodejs-consulting-1?hsLang=en-us)

Showcase

![Yum! Brands](https://www.bitovi.com/hubfs/yum-showcase-link-1.png)

[View case study](https://www.bitovi.com/en/bitovi-yum-case-study?hsLang=en-us)

More Projects

- [![Levi's](https://www.bitovi.com/hubfs/levis.svg)](https://www.bitovi.com/web-application-consulting-work/levis-ecommerce-responsive-redesign?hsLang=en-us)
- [![Christie's International Real Estate](https://www.bitovi.com/hubfs/christies.svg)](https://design.bitovi.com/christies)
- [![BAFS](https://www.bitovi.com/hubfs/bafs.svg)](https://www.bitovi.com/ux-design-consulting/ux-case-studies/bafs-ppp?hsLang=en-us)
- [View more
  
  →
  
  ](https://www.bitovi.com/our-software-consulting-work?hsLang=en-us)

Open Source Tools

We build powerful tools and open source them to support the community.

[See what we've built →](https://www.bitovi.com/open-source?hsLang=en-us)

- [![Blog](https://www.bitovi.com/hubfs/icon%20-%20blog.svg)
  
  BlogWe post about perfecting software delivery
  
  ](https://bitovi.com/blog)
- [![Partnerships](https://www.bitovi.com/hubfs/Handshake-1.svg)
  
  PartnershipsLearn about Bitovi's technology partners
  
  ](https://www.bitovi.com/partnerships?hsLang=en-us)
- [![Academy](https://www.bitovi.com/hubfs/limbo/icons/icon-academy.svg)
  
  AcademyFree courses to build delivery skills
  
  ](https://www.bitovi.com/academy/?hsLang=en-us)
- [![Open source tools](https://www.bitovi.com/hubfs/icon%20-%20open%20source.svg)
  
  Open source toolsUse our tools or contribute to our open source mission.
  
  ](https://www.bitovi.com/open-source?hsLang=en-us)

Let's Connect

- [![Discord](https://www.bitovi.com/hubfs/DiscordLogo.svg)
  
  Discord
  
  ](https://discord.gg/J7ejFsZnJ4)
- [![LinkedIn](https://www.bitovi.com/hubfs/LinkedinLogo.svg)
  
  LinkedIn
  
  ](https://www.linkedin.com/company/bitovi/)
- [![GitHub](https://www.bitovi.com/hubfs/GithubLogo.svg)
  
  GitHub
  
  ](https://github.com/bitovi/)

![Eggbot](https://www.bitovi.com/hubfs/build_assets/bitovi-limbo-cms-react/338/js_client_assets/assets/eggbot-LTGhdSGL.png)

Name *

Work Email *

Phone

What's your project?

Send

### Contact Us

(312) 620-0386contact@bitovi.com

- [ Advanced JavaScript ](https://www.bitovi.com/academy/learn-advanced-javascript.html?hsLang=en-us)
- [ DOM and jQuery ](https://www.bitovi.com/academy/learn-dom-jquery.html?hsLang=en-us)
- [ Debug JavaScript ](https://www.bitovi.com/academy/learn-to-debug-javascript.html?hsLang=en-us)
- [ Learn TypeScript ](https://www.bitovi.com/academy/learn-typescript.html?hsLang=en-us)
- [ Learn React Native ](https://www.bitovi.com/academy/learn-react-native.html?hsLang=en-us)
- [ Learn Web Components ](https://www.bitovi.com/academy/learn-web-components.html?hsLang=en-us)
- [ Agile Program Management With Jira ](https://www.bitovi.com/academy/learn-agile-program-management-with-jira.html?hsLang=en-us)
- [ Learn RxJS ](https://www.bitovi.com/academy/learn-rxjs.html?hsLang=en-us)
- [ Giving a Training ](https://www.bitovi.com/academy/training.html?hsLang=en-us)
- [ Learn Technology Consulting ](https://www.bitovi.com/academy/learn-technology-consulting.html?hsLang=en-us)
- [ Learn React ](https://www.bitovi.com/academy/learn-react.html?hsLang=en-us)
- [ Learn NgRx ](https://www.bitovi.com/academy/learn-ngrx.html?hsLang=en-us) 
    - [ Why NgRx ](https://www.bitovi.com/academy/learn-ngrx/why-ngrx.html?hsLang=en-us)
    - [ Getting Started ](https://www.bitovi.com/academy/learn-ngrx/getting-started.html?hsLang=en-us)
    - [ Installing NgRx ](https://www.bitovi.com/academy/learn-ngrx/ngrx-init.html?hsLang=en-us)
    - [ Creating Actions ](https://www.bitovi.com/academy/learn-ngrx/create-actions.html?hsLang=en-us)
    - [ Dispatching Actions ](https://www.bitovi.com/academy/learn-ngrx/dispatch-actions.html?hsLang=en-us)
    - [ Testing Actions ](https://www.bitovi.com/academy/learn-ngrx/test-actions.html?hsLang=en-us)
    - [ Create an API Effect ](https://www.bitovi.com/academy/learn-ngrx/create-api-effects.html?hsLang=en-us)
    - [ Testing API Effects ](https://www.bitovi.com/academy/learn-ngrx/test-api-effects.html?hsLang=en-us)
    - [ Creating Redirect Effects ](https://www.bitovi.com/academy/learn-ngrx/create-redirect-effects.html?hsLang=en-us)
    - [ Testing Redirect Effects ](https://www.bitovi.com/academy/learn-ngrx/test-redirect-effects.html?hsLang=en-us)
    - [ Create a Reducer ](https://www.bitovi.com/academy/learn-ngrx/create-reducer.html?hsLang=en-us)
    - [ Testing Reducers ](https://www.bitovi.com/academy/learn-ngrx/test-reducer.html?hsLang=en-us)
    - [ Creating Selectors ](https://www.bitovi.com/academy/learn-ngrx/create-selectors.html?hsLang=en-us)
    - [ Testing Selectors ](https://www.bitovi.com/academy/learn-ngrx/test-selectors.html?hsLang=en-us)
    - [ Using Selectors ](https://www.bitovi.com/academy/learn-ngrx/use-selectors.html?hsLang=en-us)
    - [ Testing Selector Use ](https://www.bitovi.com/academy/learn-ngrx/test-used-selectors.html?hsLang=en-us)
- [ Learn Graphql ](https://www.bitovi.com/academy/learn-graphql.html?hsLang=en-us)
- [ Learn Docker ](https://www.bitovi.com/academy/learn-docker.html?hsLang=en-us)
- [ Learn CanJS ](https://www.bitovi.com/academy/learn-canjs.html?hsLang=en-us)
- [ Learn Angular ](https://www.bitovi.com/academy/learn-angular.html?hsLang=en-us)
- [ Advanced TypeScript ](https://www.bitovi.com/academy/advanced-typescript.html?hsLang=en-us)

[View More Courses](https://www.bitovi.com/academy/#courses) [ ]

# Testing Actions `page`

Learn how to write unit tests for NgRx Actions.

> **Quick Start**: You can checkout [this branch](https://github.com/bitovi/angular-ngrx-chat/tree/dispatch-actions) to get your codebase ready to work on this section.

## Overview

1. Verify `LoginActions.logout` Action dispatches when calling `DashboardComponent`’s `logout()`.
2. Verify `LoginActions.login` Action dispatches with form payload when calling `LoginComponent`’s `login()`.

## 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.

## Introduction

Each implementation section will be paired with a testing section. These testing sections will go over the basics of how to test the implementation and use of NgRx. Before continuing, you should have an intermediate understanding of the following:

1. [Angular TestBeds](https://angular.io/guide/testing-utility-apis) - Although `TestBeds` aren’t required for testing Angular applications, and [there are ways to test NgRx without a TestBed](https://ngrx.io/guide/store/testing#testing-without-testbed), we use `TestBeds` throughout this course.
2. [Jasmine Unit Tests](https://github.com/jasmine/jasmine#a-javascript-testing-framework) - Throughout this course, we will be using [Jasmine to test our application](https://jasmine.github.io/tutorials/your_first_suite). Although the syntax will differ slightly between different testing tools such as [Mocha](https://mochajs.org/) and [Jest](https://jestjs.io/), the concepts used throughout this course will apply to whatever solution you use in your future projects.

## Description

In this section, we will write unit tests involving Actions. When testing Actions, we don’t typically test Actions directly. Instead, we test their use in Components, Effects, and Reducers. Throughout this course we will cover all these situations. For this section, we will verify that Actions are dispatched when expected by checking their use in Components.

## Update `dashboard.component.spec.ts`

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

### Setting Up our `TestBed`

When unit testing in general, we should use stubs to isolate parts of our application. Luckily NgRx makes this process simple by providing a way to create a mock `Store`:

1. [MockStore](https://ngrx.io/api/store/testing/MockStore) - `MockStore` extends the `Store` class and [provides stubs for its methods](https://ngrx.io/guide/store/testing).
2. [provideMockStore()](https://ngrx.io/api/store/testing/provideMockStore) - Generates a `MockStore` instance given a [configuration](https://ngrx.io/api/store/testing/MockStoreConfig).

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 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: [provideMockStore({})],
    }).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', () => {
      // TODO: Spy on dispatching action

      component.logout();

      // TODO: Verify that LoginActions.logout action was dispatched
    });
  });

  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
    });
  });
});

```

### Verify `LoginActions.logout` Action Dispatches Properly

In `src/app/dashboard/dashboard.component.spec.ts`, there is a `describe` block with a `TODO`: "Spy on dispatching action". We will create a [spy](https://jasmine.github.io/tutorials/your_first_suite#:~:text=%C2%B6-,Spies,-Jasmine%20has%20test) to track when our `MockStore` dispatches an Action:

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 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: [provideMockStore({})],
    }).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();

      // TODO: Verify that LoginActions.logout action was dispatched
    });
  });

  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
    });
  });
});

```

Next we will verify that the expected Action was dispatched after `logout()` is called:

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: [provideMockStore({})],
    }).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
    });
  });
});

```

> Note that there are 2 more pending `TODO`s in `src/app/dashboard/dashboard.component.spec.ts` that will be resolved in upcoming sections. For now we’ll only be testing our Actions.

## Update `login.component.spec.ts`

We will walk through updating `src/app/login/login.component.spec.ts` to run tests for your Actions.

### Setting Up our `TestBed`

src/app/login/login.component.spec.ts

```
// src/app/login/login.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { LoginComponent } from './login.component';
import { MockStore, provideMockStore } from '@ngrx/store/testing';
import * as fromLogin from '../store/login/login.reducer';

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

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [provideMockStore({}), FormBuilder],
      imports: [ReactiveFormsModule],
    }).compileComponents();

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

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

  describe('submit()', () => {
    it('should mark form as touched', () => {
      expect(component['form'].touched).toBe(false);
      component.submit();
      expect(component['form'].touched).toBe(true);
    });

    describe('when form is valid', () => {
      const mock = {
        username: 'some-username',
        password: 'some-password',
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should have a valid form', () => {
        // Verify that form is truly valid for upcoming tests
        expect(component['form'].valid).toBe(true);
      });

      it('should dispatch LoginActions.login', () => {
        // TODO: Spy on dispatching action

        component.submit();

        // TODO: Verify that LoginActions.login action was dispatched
      });
    });

    describe('when form is NOT valid', () => {
      const mock = {
        username: 'some-username',
        password: '', // password is required
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should NOT have a valid form', () => {
        // Verify that form is truly invalid for upcoming tests
        expect(component['form'].valid).toBe(false);
      });

      it('should NOT dispatch LoginActions.login', () => {
        // TODO: Spy on dispatching action

        component.submit();

        // TODO: Verify that no action was dispatched
      });
    });
  });
});

```

### Verify `LoginActions.login` Action Dispatches Properly With Form Payload

Unlike the `LoginActions.logout` Action, `LoginActions.login` Action requires a payload when dispatched. In our test, we will pass the state of our form:

src/app/login/login.component.spec.ts

```
// src/app/login/login.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { LoginComponent } from './login.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('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let store: MockStore<fromLogin.LoginPartialState>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [provideMockStore({}), FormBuilder],
      imports: [ReactiveFormsModule],
    }).compileComponents();

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

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

  describe('submit()', () => {
    it('should mark form as touched', () => {
      expect(component['form'].touched).toBe(false);
      component.submit();
      expect(component['form'].touched).toBe(true);
    });

    describe('when form is valid', () => {
      const mock = {
        username: 'some-username',
        password: 'some-password',
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should have a valid form', () => {
        // Verify that form is truly valid for upcoming tests
        expect(component['form'].valid).toBe(true);
      });

      it('should dispatch LoginActions.login', () => {
        const spy = spyOn(store, 'dispatch');

        component.submit();

        expect(spy).toHaveBeenCalledOnceWith(LoginActions.login(mock));
      });
    });

    describe('when form is NOT valid', () => {
      const mock = {
        username: 'some-username',
        password: '', // password is required
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should NOT have a valid form', () => {
        // Verify that form is truly invalid for upcoming tests
        expect(component['form'].valid).toBe(false);
      });

      it('should NOT dispatch LoginActions.login', () => {
        // TODO: Spy on dispatching action

        component.submit();

        // TODO: Verify that no action was dispatched
      });
    });
  });
});

```

### Verify `LoginActions.login` Action Does NOT Dispatch When Necessary

src/app/login/login.component.spec.ts

```
// src/app/login/login.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { LoginComponent } from './login.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('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let store: MockStore<fromLogin.LoginPartialState>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [provideMockStore({}), FormBuilder],
      imports: [ReactiveFormsModule],
    }).compileComponents();

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

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

  describe('submit()', () => {
    it('should mark form as touched', () => {
      expect(component['form'].touched).toBe(false);
      component.submit();
      expect(component['form'].touched).toBe(true);
    });

    describe('when form is valid', () => {
      const mock = {
        username: 'some-username',
        password: 'some-password',
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should have a valid form', () => {
        // Verify that form is truly valid for upcoming tests
        expect(component['form'].valid).toBe(true);
      });

      it('should dispatch LoginActions.login', () => {
        const spy = spyOn(store, 'dispatch');

        component.submit();

        expect(spy).toHaveBeenCalledOnceWith(LoginActions.login(mock));
      });
    });

    describe('when form is NOT valid', () => {
      const mock = {
        username: 'some-username',
        password: '', // password is required
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should NOT have a valid form', () => {
        // Verify that form is truly invalid for upcoming tests
        expect(component['form'].valid).toBe(false);
      });

      it('should NOT dispatch LoginActions.login', () => {
        const spy = spyOn(store, 'dispatch');

        component.submit();

        expect(spy).not.toHaveBeenCalled();
      });
    });
  });
});

```

## 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: [provideMockStore({})],
    }).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
    });
  });
});

```

src/app/login/login.component.spec.ts

```
// src/app/login/login.component.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormBuilder, ReactiveFormsModule } from '@angular/forms';
import { LoginComponent } from './login.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('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let store: MockStore<fromLogin.LoginPartialState>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [LoginComponent],
      providers: [provideMockStore({}), FormBuilder],
      imports: [ReactiveFormsModule],
    }).compileComponents();

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

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

  describe('submit()', () => {
    it('should mark form as touched', () => {
      expect(component['form'].touched).toBe(false);
      component.submit();
      expect(component['form'].touched).toBe(true);
    });

    describe('when form is valid', () => {
      const mock = {
        username: 'some-username',
        password: 'some-password',
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should have a valid form', () => {
        // Verify that form is truly valid for upcoming tests
        expect(component['form'].valid).toBe(true);
      });

      it('should dispatch LoginActions.login', () => {
        const spy = spyOn(store, 'dispatch');

        component.submit();

        expect(spy).toHaveBeenCalledOnceWith(LoginActions.login(mock));
      });
    });

    describe('when form is NOT valid', () => {
      const mock = {
        username: 'some-username',
        password: '', // password is required
      };

      beforeEach(() => {
        component['form'].setValue(mock);
      });

      it('should NOT have a valid form', () => {
        // Verify that form is truly invalid for upcoming tests
        expect(component['form'].valid).toBe(false);
      });

      it('should NOT dispatch LoginActions.login', () => {
        const spy = spyOn(store, 'dispatch');

        component.submit();

        expect(spy).not.toHaveBeenCalled();
      });
    });
  });
});

```

> **Wrap-up**: By the end of this section, your code should match [this branch](https://github.com/bitovi/angular-ngrx-chat/tree/test-actions). You can also compare the [code changes for our solution to this section](https://github.com/bitovi/angular-ngrx-chat/compare/dispatch-actions...test-actions) on GitHub or you can use the following command in your terminal:

```
git diff origin/test-actions
```

###### On this page...

###### Get help

If you need [help](https://discord.gg/J7ejFsZnJ4) with an exercise, please reach out to us on [Discord](https://discord.gg/J7ejFsZnJ4)

If you find a bug, please [create an issue](https://github.com/bitovi/academy/issues/new) or email [contact@bitovi.com](mailto:contact@bitovi.com?Subject=Training%20Suggestion)

## Join the Bitovi Newsletter

 Do you want to receive updates about new Bitovi Academy courses, Bitovi open source projects, and dev community news? If so, please provide your email. We won’t spam you or give it away for any reason!  
*By providing your email you agree to receive communications from Bitovi*

![](https://bitovi.github.io/academy/static/img/close.svg)

```json
{
     "@context": "http://schema.org",
     "@type": "Organization",
     "mainEntityOfPage":{
          "@type":"WebPage",
          "@id":"https://www.bitovi.com/academy/learn-ngrx/test-actions.html",
          "description": "Learn how to write unit tests for NgRx Actions. 
"
     },
     "url": "http://bitovi.com",
     "logo": "https://www.bitovi.com/hubfs/bitovi-logo-23-1.svg",
     "image": "https://www.bitovi.com/hubfs/bitovi-logo-x2.png",
     "name": "Bitovi Web App Consulting",
     "alternateName": "Bitovi",
      "naics": "541511",
      "description": "Bitovi is a UX, UI design and front-end JavaScript development consulting company",
     "address": {
          "@type": "PostalAddress",
          "streetAddress": "1134 Pine Tree Lane ",
          "addressLocality": "Libertyville",
          "addressRegion": "IL",
          "addressCountry": "United States",
          "postalCode": "60048"
     },
     "areaServed": {
          "@type": "GeoCircle",
          "geoMidpoint": {
               "@type": "GeoCoordinates",
               "latitude": "41.8781",
               "longitude": "87.6298"
          },
          "geoRadius": "5000 km"
     },
     "telephone": "312-620-0386",
     "email": "contact@bitovi.com",
     "sameAs": [
          "https://www.facebook.com/BitoviLLC/",
          "https://twitter.com/bitovi",
          "https://www.linkedin.com/company/bitovi"
     ]
}
```