如何测试其他函数调用的异步函数?

How to test asynchronous functions called in other functions?

我的组件中有一个函数调用服务中的另一个异步函数,我无法测试订阅内部发生的情况。谁能帮我解决这个问题?

我需要知道是否调用了 this.auth.sendPasswordResetEmail (data.email) 函数。

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

在这部分我尝试做一些事情,但没有成功。

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

  });
});

尝试添加日志并查看顺序。以下日志应该可以帮助您调试问题,我认为 fixture.whenStable 也会解决该问题。

这应该可以帮助您调试问题:

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');
      // I am thinking you need this await fixture.whenStable 
      // to wait for the promise of the async callback in the subscribe block
      // to complete before making assertions.
      await fixture.whenStable();
      expect(authServiceMock.sendPasswordResetEmail).toHaveBeenCalled();
    });