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

- ![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 API Effects `page`

Learn how to write unit tests for NgRx Effects.

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

## Overview

1. Verify `LoginActions.loginSuccess` Action dispatches when API request is successful.
2. Verify `LoginActions.loginFailure` Action dispatches when API request fails.
3. Verify `LoginActions.logoutSuccess` Action dispatches when API request is successful.
4. Verify `LoginActions.logoutFailure` Action dispatches when API request fails.

## 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 Effects, we will verify that a specific Action gets dispatched depending on the circumstances when an Effect is triggered. In our case, we are working with Effects that depend on an API response. We can take advantage of spies to simulate different API responses.

## Update `login.effects.spec.ts`

We will walk through updating `src/app/store/login/login.effects.spec.ts` to run tests for your Effects.

### Setting Up our `TestBed`

When testing Effects, we will need to mock the [`Actions` class](https://ngrx.io/api/effects/Actions) since it plays a major role on [how Effects work](https://ngrx.io/guide/effects#writing-effects). NgRx provides a convenient way to do this through [`provideMockActions()`](https://ngrx.io/api/effects/testing/provideMockActions):

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

```
// src/app/store/login/login.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of } from 'rxjs';
import { LoginEffects } from './login.effects';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from './login.actions';

describe('LoginEffects', () => {
  let actions$: Observable<Action>;
  let effects: LoginEffects;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        LoginEffects,
        provideMockActions(() => actions$),
        provideMockStore(),
      ],
    });

    effects = TestBed.inject(LoginEffects);
  });

  describe('login$', () => {
    beforeEach(() => {
      actions$ = of(
        LoginActions.login({
          username: 'some-username',
          password: 'some-password',
        })
      );
    });
  });
});

```

One way to mock `Actions` is to set `actions$` to be an `Observable` that emits whatever Action we want for our tests:

```
// Note: This example code is not part of our application repo or solution

describe('login$', () => {
  beforeEach(() => {
    // Mock `actions$` with `Observable` that emits an Action
    // and its payload for upcoming tests
    actions$ = of(
      LoginActions.login({
        username: 'some-username',
        password: 'some-password',
      })
    );
  });

  it('should...', () => {
    effects.login$.subscribe(action => {
      // This `action` value will be whatever Action is dispatched
      // based on our mocked value for `actions$`
    });
  });
});

```

### Mocking `LoginService`

Since our Effects use `LoginService` from `ngx-learn-ngrx`, we will need to also mock this `Service`:

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

```
// src/app/store/login/login.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of } from 'rxjs';
import { LoginEffects } from './login.effects';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from './login.actions';
import { Credentials, LoginService } from 'ngx-learn-ngrx';

const mockLoginService = {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  login: (credentials: Credentials) => {
    return of({ userId: 'some-user-id', token: 'some-token' });
  },
  logout: () => of(null),
} as LoginService;

describe('LoginEffects', () => {
  let actions$: Observable<Action>;
  let effects: LoginEffects;

  let loginService: LoginService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        LoginEffects,
        provideMockActions(() => actions$),
        provideMockStore(),
        {
          provide: LoginService,
          useValue: mockLoginService,
        },
      ],
    });

    effects = TestBed.inject(LoginEffects);
    loginService = TestBed.inject(LoginService);
  });

  describe('login$', () => {
    beforeEach(() => {
      actions$ = of(
        LoginActions.login({
          username: 'some-username',
          password: 'some-password',
        })
      );
    });
  });
});

```

### Verifying `LoginEffects.login$` Effect Dispatches `LoginActions.loginSuccess` When API Request is Successful

We will create a [spy](https://jasmine.github.io/tutorials/your_first_suite#:~:text=%C2%B6-,Spies,-Jasmine%20has%20test) to verify `LoginService.login()` is called with the expected arguments. Then we will verify that `LoginActions.loginSuccess` is dispatched with the proper payload:

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

```
// src/app/store/login/login.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of } from 'rxjs';
import { LoginEffects } from './login.effects';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from './login.actions';
import { Credentials, LoginService } from 'ngx-learn-ngrx';

const mockLoginService = {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  login: (credentials: Credentials) => {
    return of({ userId: 'some-user-id', token: 'some-token' });
  },
  logout: () => of(null),
} as LoginService;

describe('LoginEffects', () => {
  let actions$: Observable<Action>;
  let effects: LoginEffects;

  let loginService: LoginService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        LoginEffects,
        provideMockActions(() => actions$),
        provideMockStore(),
        {
          provide: LoginService,
          useValue: mockLoginService,
        },
      ],
    });

    effects = TestBed.inject(LoginEffects);
    loginService = TestBed.inject(LoginService);
  });

  describe('login$', () => {
    beforeEach(() => {
      actions$ = of(
        LoginActions.login({
          username: 'some-username',
          password: 'some-password',
        })
      );
    });

    it('should get dispatch LoginActions.loginSuccess on api success', done => {
      const spy = spyOn(loginService, 'login').and.callThrough();

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

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

        done();
      });
    });
  });
});

```

> Note that we are taking advantage of the [`done()` callback to write an asynchronous test](https://jasmine.github.io/tutorials/async#callbacks). This is common when writing tests where you subscribe to an `Observable` to perform a test.

### Verifying `LoginEffects.login$` Effect Dispatches `LoginActions.loginFailure` When API Request is NOT Successful

To verify different behaviors for `LoginService`, we can take advantage of [spies](https://jasmine.github.io/tutorials/your_first_suite#:~:text=%C2%B6-,Spies,-Jasmine%20has%20test) to return a different return value:

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

```
// src/app/store/login/login.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of, throwError } from 'rxjs';
import { LoginEffects } from './login.effects';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from './login.actions';
import { Credentials, LoginService } from 'ngx-learn-ngrx';

const mockLoginService = {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  login: (credentials: Credentials) => {
    return of({ userId: 'some-user-id', token: 'some-token' });
  },
  logout: () => of(null),
} as LoginService;

describe('LoginEffects', () => {
  let actions$: Observable<Action>;
  let effects: LoginEffects;

  let loginService: LoginService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        LoginEffects,
        provideMockActions(() => actions$),
        provideMockStore(),
        {
          provide: LoginService,
          useValue: mockLoginService,
        },
      ],
    });

    effects = TestBed.inject(LoginEffects);
    loginService = TestBed.inject(LoginService);
  });

  describe('login$', () => {
    beforeEach(() => {
      actions$ = of(
        LoginActions.login({
          username: 'some-username',
          password: 'some-password',
        })
      );
    });

    it('should get dispatch LoginActions.loginSuccess on api success', done => {
      const spy = spyOn(loginService, 'login').and.callThrough();

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

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

        done();
      });
    });

    it('should get dispatch LoginActions.loginFailure on api error', done => {
      const spy = spyOn(loginService, 'login').and.returnValue(
        throwError(() => new Error('some error message'))
      );

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

        expect(action).toEqual(
          LoginActions.loginFailure({
            errorMsg: 'some error message',
          })
        );

        done();
      });
    });
  });
});

```

### Verifying `LoginEffects.logout$` Effect Dispatches `LoginActions.logoutSuccess` When API Request is Successful

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

```
// src/app/store/login/login.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of, throwError } from 'rxjs';
import { LoginEffects } from './login.effects';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from './login.actions';
import { Credentials, LoginService } from 'ngx-learn-ngrx';

const mockLoginService = {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  login: (credentials: Credentials) => {
    return of({ userId: 'some-user-id', token: 'some-token' });
  },
  logout: () => of(null),
} as LoginService;

describe('LoginEffects', () => {
  let actions$: Observable<Action>;
  let effects: LoginEffects;

  let loginService: LoginService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        LoginEffects,
        provideMockActions(() => actions$),
        provideMockStore(),
        {
          provide: LoginService,
          useValue: mockLoginService,
        },
      ],
    });

    effects = TestBed.inject(LoginEffects);
    loginService = TestBed.inject(LoginService);
  });

  describe('login$', () => {
    beforeEach(() => {
      actions$ = of(
        LoginActions.login({
          username: 'some-username',
          password: 'some-password',
        })
      );
    });

    it('should get dispatch LoginActions.loginSuccess on api success', done => {
      const spy = spyOn(loginService, 'login').and.callThrough();

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

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

        done();
      });
    });

    it('should get dispatch LoginActions.loginFailure on api error', done => {
      const spy = spyOn(loginService, 'login').and.returnValue(
        throwError(() => new Error('some error message'))
      );

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

        expect(action).toEqual(
          LoginActions.loginFailure({
            errorMsg: 'some error message',
          })
        );

        done();
      });
    });
  });

  describe('logout$', () => {
    beforeEach(() => {
      actions$ = of(LoginActions.logout());
    });

    it('should get dispatch LoginActions.logoutSuccess on api success', done => {
      effects.logout$.subscribe(action => {
        expect(action).toEqual(LoginActions.logoutSuccess());
        done();
      });
    });
  });
});

```

### Verifying `LoginEffects.logout$` Effect Dispatches `LoginActions.logoutFailure` When API Request is NOT Successful

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

```
// src/app/store/login/login.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of, throwError } from 'rxjs';
import { LoginEffects } from './login.effects';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from './login.actions';
import { Credentials, LoginService } from 'ngx-learn-ngrx';

const mockLoginService = {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  login: (credentials: Credentials) => {
    return of({ userId: 'some-user-id', token: 'some-token' });
  },
  logout: () => of(null),
} as LoginService;

describe('LoginEffects', () => {
  let actions$: Observable<Action>;
  let effects: LoginEffects;

  let loginService: LoginService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        LoginEffects,
        provideMockActions(() => actions$),
        provideMockStore(),
        {
          provide: LoginService,
          useValue: mockLoginService,
        },
      ],
    });

    effects = TestBed.inject(LoginEffects);
    loginService = TestBed.inject(LoginService);
  });

  describe('login$', () => {
    beforeEach(() => {
      actions$ = of(
        LoginActions.login({
          username: 'some-username',
          password: 'some-password',
        })
      );
    });

    it('should get dispatch LoginActions.loginSuccess on api success', done => {
      const spy = spyOn(loginService, 'login').and.callThrough();

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

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

        done();
      });
    });

    it('should get dispatch LoginActions.loginFailure on api error', done => {
      const spy = spyOn(loginService, 'login').and.returnValue(
        throwError(() => new Error('some error message'))
      );

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

        expect(action).toEqual(
          LoginActions.loginFailure({
            errorMsg: 'some error message',
          })
        );

        done();
      });
    });
  });

  describe('logout$', () => {
    beforeEach(() => {
      actions$ = of(LoginActions.logout());
    });

    it('should get dispatch LoginActions.logoutSuccess on api success', done => {
      effects.logout$.subscribe(action => {
        expect(action).toEqual(LoginActions.logoutSuccess());
        done();
      });
    });

    it('should get dispatch LoginActions.logoutFailure on api error', done => {
      const spy = spyOn(loginService, 'logout').and.returnValue(
        throwError(() => new Error('some error message'))
      );

      effects.logout$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith();

        expect(action).toEqual(
          LoginActions.logoutFailure({
            errorMsg: 'some error message',
          })
        );

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

```
// src/app/store/login/login.effects.spec.ts

import { TestBed } from '@angular/core/testing';
import { provideMockActions } from '@ngrx/effects/testing';
import { Observable, of, throwError } from 'rxjs';
import { LoginEffects } from './login.effects';
import { Action } from '@ngrx/store';
import { provideMockStore } from '@ngrx/store/testing';
import * as LoginActions from './login.actions';
import { Credentials, LoginService } from 'ngx-learn-ngrx';

const mockLoginService = {
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  login: (credentials: Credentials) => {
    return of({ userId: 'some-user-id', token: 'some-token' });
  },
  logout: () => of(null),
} as LoginService;

describe('LoginEffects', () => {
  let actions$: Observable<Action>;
  let effects: LoginEffects;

  let loginService: LoginService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [],
      providers: [
        LoginEffects,
        provideMockActions(() => actions$),
        provideMockStore(),
        {
          provide: LoginService,
          useValue: mockLoginService,
        },
      ],
    });

    effects = TestBed.inject(LoginEffects);
    loginService = TestBed.inject(LoginService);
  });

  describe('login$', () => {
    beforeEach(() => {
      actions$ = of(
        LoginActions.login({
          username: 'some-username',
          password: 'some-password',
        })
      );
    });

    it('should get dispatch LoginActions.loginSuccess on api success', done => {
      const spy = spyOn(loginService, 'login').and.callThrough();

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

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

        done();
      });
    });

    it('should get dispatch LoginActions.loginFailure on api error', done => {
      const spy = spyOn(loginService, 'login').and.returnValue(
        throwError(() => new Error('some error message'))
      );

      effects.login$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith({
          username: 'some-username',
          password: 'some-password',
        });

        expect(action).toEqual(
          LoginActions.loginFailure({
            errorMsg: 'some error message',
          })
        );

        done();
      });
    });
  });

  describe('logout$', () => {
    beforeEach(() => {
      actions$ = of(LoginActions.logout());
    });

    it('should get dispatch LoginActions.logoutSuccess on api success', done => {
      effects.logout$.subscribe(action => {
        expect(action).toEqual(LoginActions.logoutSuccess());
        done();
      });
    });

    it('should get dispatch LoginActions.logoutFailure on api error', done => {
      const spy = spyOn(loginService, 'logout').and.returnValue(
        throwError(() => new Error('some error message'))
      );

      effects.logout$.subscribe(action => {
        expect(spy).toHaveBeenCalledOnceWith();

        expect(action).toEqual(
          LoginActions.logoutFailure({
            errorMsg: 'some error message',
          })
        );

        done();
      });
    });
  });
});

```

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

```
git diff origin/test-api-effects
```

###### 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-api-effects.html",
          "description": "Learn how to write unit tests for NgRx Effects. 
"
     },
     "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"
     ]
}
```