What is the best approach for testing asynchronous functions that are invoked within other functions?

I am facing an issue in my component where I have a function that calls another asynchronous function in a service. Unfortunately, testing what happens inside the subscription is posing a challenge for me. Can anyone provide guidance on how to test this?

I specifically want to verify if the function this.auth.sendPasswordResetEmail(data.email) is being called.

login.component.ts

async forgotPassword() {

    const {
      ["modules.organizations.sign_in.forgot_password.alert.title"]: title,
      ["modules.organizations.sign_in.forgot_password.alert.message"]: message,
      ["modules.organizations.sign_in.forgot_password.alert.message.success"]: success,
    } = await this.translate.get([
      "modules.organizations.sign_in.forgot_password.alert.title",
      "modules.organizations.sign_in.forgot_password.alert.message",
      "modules.organizations.sign_in.forgot_password.alert.message.success"
    ]).pipe(first()).toPromise()

    this.alert.input(title, message).afterClosed()
    .subscribe(async data => {
      if(!data.ok) return 
      if(data.input == null) return 
      const loading = this.alert.loading()
      try {
        await this.auth.sendPasswordResetEmail(data.email)
        loading.close()
        this.alert.error(title, success)
      } catch (error) {
        loading.close()
        this.alert.error(null, error)
      }
    })
  }

In this segment, I tried to troubleshoot the problem without much success.

login.component.spec.ts

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let alertServiceMock: jasmine.SpyObj<any>;
  let authServiceMock: jasmine.SpyObj<any>;
  let dialogRefSpy: jasmine.SpyObj<any>;
  let router: Router;

  dialogRefSpy = jasmine.createSpy();
  dialogRefSpy.component = {title: 'error', message: 'error'};
  dialogRefSpy.afterClosed = () => of(true);

  const matDialogSpy = jasmine.createSpyObj('MatDialog', [
    'open',
    'close',
  ]);
  matDialogSpy.open.and.returnValue(dialogRefSpy);
  matDialogSpy.close.and.returnValue(dialogRefSpy);

  beforeEach(waitForAsync(() => {

    authServiceMock = jasmine.createSpyObj('AuthService',[
      'createUserWithEmailAndPassword',
      'updateProfile',
      'signInWithEmailAndPassword',
      'signInWithPopup',
      'sendPasswordResetEmail'
    ]);
    authServiceMock.createUserWithEmailAndPassword.and.returnValue(true);
    authServiceMock.updateProfile.and.returnValue(true);
    authServiceMock.signInWithEmailAndPassword.and.returnValue(true);
    authServiceMock.signInWithPopup.and.returnValue(true);
    authServiceMock.sendPasswordResetEmail.and.returnValue(true);
    
    alertServiceMock = jasmine.createSpyObj('AlertService',[
      'message',
      'error',
      'input',
      'password',
      'loading',
      'confirmation'
    ]);
    alertServiceMock.error.and.returnValue(matDialogSpy.open(AlertComponent, {
      data: {
        type: 'error',
        title: 'error',
        error: 'description'
      },
      disableClose: true
    }));
    alertServiceMock.input.and.returnValue(matDialogSpy.open(AlertComponent, {
      data: { 
        type: 'input', 
        title: 'title', 
        description: 'description'
      },
      disableClose: true
    }));
    alertServiceMock.loading.and.returnValue(matDialogSpy);

  TestBed.configureTestingModule({
      imports: [
        NoopAnimationsModule,
        AngularFireModule.initializeApp(environment.firebase),
        AngularFirestoreModule,
        OverlayModule,
        RouterTestingModule,
        MatDialogModule,
        TranslateModule.forRoot({
          loader: {
            provide: TranslateLoader,
            useClass: TranslateFakeLoader
          }
        })
      ],
      schemas: [CUSTOM_ELEMENTS_SCHEMA],
      declarations: [ LoginComponent ],
      providers: [
        { provide: AlertService, useValue: alertServiceMock },
        { provide: AuthService, useValue: authServiceMock },
        { provide: MatDialog, useValue: matDialogSpy },
        { provide: ActivatedRoute, useFactory: () => mockActiveRoute },
        MatSnackBar,
        MatProgressBarModule,
        FirestoreService,
        FunctionsService,
        TranslateService,
      ],
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(LoginComponent);
    fireStoreService = TestBed.inject(FirestoreService);
    alertServiceMock = TestBed.inject(AlertService);
    authServiceMock = TestBed.inject(AuthService);
    functionsServiceMock = TestBed.inject(FunctionsService);
    
    router = TestBed.inject(Router);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  afterEach(() => {
    localStorage.removeItem('token');
    fixture.destroy();
  });

describe('tests on forgot password', () => {

    it('should generate a FORGOT PASSWORD ALERT to enter an EMAIL', async () => {
      await component.forgotPassword();

      expect(authServiceMock.sendPasswordResetEmail).toHaveBeenCalled();
    });

  });
});

Answer №1

Consider adding logs to track the sequence of events. The following log statements can assist you in troubleshooting the problem, and utilizing fixture.whenStable might resolve the issue.

To aid in debugging, include the following code snippet:

async forgotPassword() {
    console.log('[Component] Forgot password was called');
    const {
      ["modules.organizations.sign_in.forgot_password.alert.title"]: title,
      ["modules.organizations.sign_in.forgot_password.alert.message"]: message,
      ["modules.organizations.sign_in.forgot_password.alert.message.success"]: success,
    } = await this.translate.get([
      "modules.organizations.sign_in.forgot_password.alert.title",
      "modules.organizations.sign_in.forgot_password.alert.message",
      "modules.organizations.sign_in.forgot_password.alert.message.success"
    ]).pipe(first()).toPromise()
    console.log('translate promise was resolved');
    this.alert.input(title, message).afterClosed()
    .subscribe(async data => {
      console.log('[Component] inside of subscribe block');
      if(!data.ok) return 
      if(data.input == null) return 
      const loading = this.alert.loading()
      try {
        console.log('[Component] calling sendPasswordResetEmail');
        await this.auth.sendPasswordResetEmail(data.email)
        console.log('[Component] sendPasswordResetEmail completed');
        loading.close()
        this.alert.error(title, success)
      } catch (error) {
        loading.close()
        this.alert.error(null, error)
      }
    })
  }
it('should generate a FORGOT PASSWORD ALERT to enter an EMAIL', async () => {
      console.log('[Test] Calling forgotPassword');
      await component.forgotPassword();
      console.log('[Test] forgotPassword was called');
      // It is recommended to use await fixture.whenStable 
      // to ensure the async callback in the subscribe block has completed
      // before performing any assertions.
      await fixture.whenStable();
      expect(authServiceMock.sendPasswordResetEmail).toHaveBeenCalled();
    });

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Jest and Angular Testing: Resolving the TypeError - Function Not Found

I am working with 2 Angular services: TestService is dependent on the HelperService and utilizes its methods. import { HelperService } from "./helper.service"; @Injectable({ providedIn: 'root' }) export class TestService { ...

typescript: exploring the world of functions, overloads, and generics

One interesting feature of Typescript is function overloading, and it's possible to create a constant function with multiple overloads like this: interface FetchOverload { (action: string, method: 'post' | 'get'): object; (acti ...

What is the process for integrating Typescript into a Quasar 2 project that is utilizing Vite as its build tool?

The Quasar 2 documentation provides in-depth guidance on integrating Typescript with Webpack: Unfortunately, my Quasar project is configured with Vite and I am struggling to locate resources on incorporating Typescript into an already existing project. A ...

Can including an ng-template within an *ngFor loop have any impact on performance whatsoever?

Consider the ItemComponent below, which is created multiple times: <li *ngFor="let item of items"> <app-item [data]="item"></app-item> </li> Each instance contains buttons that, when clicked, display complex modal dialogs. Wi ...

How can I structure the response from HttpClient to make it compatible with *ngFor

I need assistance in solving a minor issue. I am working with data from a REST API, which is returned as an array of objects. After receiving this data in my service, I attempt to transform it and push it to a subject to notify my component about the arriv ...

I wonder if there is a more effective way to format serial numbers

Could there be a more streamlined solution for this code snippet I have? /** * Converts serial from '86FC64484BE99E78' to '86:FC:64:48:4B:E9:9E:78' * @param serial */ private formatSerial(serial: string): string { retu ...

Discover the virtual scrolling feature in Angular's ng-select component

My web page is equipped with multiple dropdowns created using the *ngFor directive, allowing users to select multiple items from each dropdown. These dropdowns are implemented with ng-select for custom server-side search functionality. I am also intereste ...

Utilize the ng2-select component to incorporate multiple instances within a single webpage

Can someone help me figure out how to use two ng2-select components in my modal? I've checked out the documentation, but it doesn't provide any information on using more than one select. I'm not sure how to capture the selected values of ea ...

The module "@react-native-firebase/firestore" does not have the specified exported member named 'CollectionReference'. (Error code: ts2614)

Utilizing TypeScript and React Native with Firestore (without expo). When attempting to import: import { CollectionReference } from '@react-native-firebase/firestore' An error is received: The module "@react-native-firebase/firestore" does no ...

Generating a jasmine spy on a connected promise

I am currently trying to test a service that utilizes Restangular, but I am facing difficulties in creating the correct spy on chained promises. var services = angular.module('services.dashboards', ['models.dashboards', 'models.me ...

Angular Compilation Error: NG6001 - The specified class is included in the NgModule 'AppModule' declarations, however, it is not recognized as a directive, component, or pipe

My app is having trouble compiling and showing the error Error NG6001: The class NavigationMenuItemComponent is included in the declarations of the NgModule AppModule, but it is not a directive, component, or pipe. You must either remove it from the N ...

What causes a hyperlink to take longer to load when using href instead of routerLink in Angular 2+?

As I work on my web application using angular 2 and incorporating routes, I noticed a significant difference in loading speed based on how I link to another component. Initially, being new to angular, I used the href attribute like this: <a href="local ...

Error encountered when retrieving WordPress posts through GraphQL in Next.js due to an invalid `<Link>` containing a `<a>` child element

While integrating Wordpress posts into my Next.js application using the repository "https://github.com/vercel/next.js/tree/canary/examples/cms-wordpress", I encountered the error message: "Error: Invalid with child. Please remove or use ." https://i.ss ...

Issue with Vite React TypeScript XLSX: An Uncaught SyntaxError occurred as the module '/node_modules/.vite/deps/xlsx.js' does not export a feature named 'default'

While attempting to integrate the xlsx library into a project using vite, reactjs, and typescript, I encountered the following error: Uncaught SyntaxError: The requested module '/node_modules/.vite/deps/xlsx.js?v=823b22a3' does not provide an e ...

Angular2 displays an error stating that the function start.endsWith is not recognized as a valid function

After switching my base URL from / to window.document.location, I encountered the following error message: TypeError: start.endsWith is not a function Has anyone else experienced this issue with [email protected]? ...

Execute a function in Angular once two HTTP requests have been completed

Currently using Angular 4, I am looking to have a function called only after 2 or 3 specific calls have been completed. For example: this.Get.firstGet().subscribe( data => { this.test(); }); this.Get.secondGet().subscribe( data => { this.te ...

Executing ng server with --hmr results in a continuous reloading of the page during updates

After upgrading my Angular application to version 11.0.2, I decided to give the --hmr option on the CLI a try. Although it appears enabled from the CLI, any changes made to a page still result in a complete refresh in the browser. During compilation of th ...

Tips for incorporating CSS 4 custom properties into Angular components

Exploring the new possibilities of CSS 4 with custom properties, my goal is to utilize them in Angular. Traditionally, custom properties are used in the following manner: <div style="--custom:red;"> <div style="background-color: var(--custom ...

Debugging in Chrome Dev Tools is not possible when the "optimization" flag is set to true in the angular.json file

When we enable optimizations by setting `optimization = true` in angular.json, we are facing issues during debugging in Chrome DevTools. Breakpoints are either not being hit or they are on the wrong line(s) in the respective TypeScript files. "loc ...

_background-variant.scss takes precedence over background color styling

I am currently working on an Angular 8 application that utilizes Bootstrap for styling. However, there seems to be a file called _background-variant.scss in Bootstrap that is overriding the background color of my menu, and I do not want this to happen. . ...