在功能上测试模拟服务 then/catch - Angular/Karma

Test mocked service then/catch on function - Angular/Karma

我的 Angular/Karma 测试存在覆盖问题。

我创建了一个具有 signUp() 函数的组件

angularFireAuthSignOutSpyObj 是来自组件 (Firebase Auth)

this.auth 的间谍
  signUp() {
    if (this.registrationForm.valid) {
      this.auth.createUserWithEmailAndPassword
      (
        this.registrationForm.get('email')?.value,
        this.registrationForm.get('password')?.value
      )
        .then(() => {
          this.appMessage = "Account created !";
        })
        .catch((error) => {
          this.appMessage = error.message;
        });
    } else {
      this.appMessage = 'Submit logic bypassed, form invalid !'
    }
  }

我正在用 karma 测试按原样测试这个组件功能

  it('should submit registration with form values', () => {
    spyOn(component, 'signUp').and.callThrough();
    angularFireAuthSignOutSpyObj.createUserWithEmailAndPassword.and.returnValue({
      then: function () {
        return {
          catch: function () {
          }
        };
      }
    });
    component.registrationForm.controls.email.setValue('test@email.com');
    component.registrationForm.controls.password.setValue('ValidPass123');
    component.registrationForm.controls.passwordCheck.setValue('ValidPass123');
    expect(component.registrationForm.valid).toBeTruthy();
    debugElement.query(By.css("button")).triggerEventHandler("click", null);
    expect(component.signUp).toHaveBeenCalled();
    expect(component.auth.createUserWithEmailAndPassword)
      .toHaveBeenCalledWith(
        component.registrationForm.controls.email.value,
        component.registrationForm.controls.password.value)
    // expect(component.appMessage).toEqual('Account created !');
  });

你可以注意到最后一个 expect 被注释掉了,因为它 return 是一个 Error: Expected undefined to equal 'Account created !' . 这是因为即使 this.auth.createUserWithEmailAndPassword 在模拟服务 angularFireAuthSignOutSpyObj 中定义,并且使用 2 个预期参数正确调用,我无法控制定义的 thencatch 函数。

它们被定义为在 signUp() 函数中尝试访问它时不会触发错误。但是 我想做的 是触发 then(() => ...) 和 catch(() => ...) 这样我就可以 test/check app.message 已正确更新。

所有异常都有效,直到最后一个。我觉得我需要修改我的 createUserWithEmailAndPassword.and.returnValue 中的某些内容,以便可能 return 触发 then 或 catch 的内容。

    angularFireAuthSignOutSpyObj.createUserWithEmailAndPassword.and.returnValue({
      then: function () {
        return {
          catch: function () {
          }
        };
      }
    });

有人知道我如何测试组件的实际 auth.createUserWithEmailAndPassword 结果行为吗?

非常感谢!

大卫

我没有看到你创建间谍的代码。您使用 promises 而不是 Observables 也有点奇怪。但是,我会研究监视方法——而不是 class,并返回一个你控制的承诺:

const resolveFunction;
const rejectFunction;    
beforeEach(() => {
 spyOn(component.auth, 'createUserWithEmailAndPassword').and.returnValue(new Promise((resolve, reject) => {
   resolveFunction = resolve;
   rejectFunction = reject;
 })
}

现在,通过您的测试,您可以通过调用这些函数来控制 promise 何时被拒绝或解决:

it('test catch block', () => {
   // some code
   rejectFunction('some error object');
})
it('test then block', () => {
   // some code
   resolveFunction('some error object');
})

More info about creating promises manually

嘿,我只是想 post 进行更新,因为我设法做到了我所需要的。感谢@JeffryHouser 的提醒。

所以基本上我的组件最初期望来自查询的 Promise。如果结果恢复正常(UserCredentials),我们只需使用成功消息更新 appMessage 字符串。如果没有(捕获)我们 return 错误 消息 .

这些是我在测试端为了模拟解析所做的更改(promise 的正常结果,以及如何触发 catch)

  • 使用 fakeAsync()
  • 将测试设置为异步
  • 监视用户 click() 使用的每个函数
  • 指定 return 作为 angularFireAuthSignOutSpyObj.createUserWithEmailAndPassword 函数的 Promise
  • tick()
  • 模拟异步流
  • 使用 fixture.detectChanges()
  • 检测承诺流结束后的变化

appMessage 项已按照流程正确更新

这里是代码!

间谍声明

let angularFireAuthSignOutSpyObj: jasmine.SpyObj<any>;
...
 beforeEach(async () => {
    angularFireAuthSignOutSpyObj = jasmine.createSpyObj('AngularFireAuth',
      ['createUserWithEmailAndPassword']);
    ...
      });

用户凭据项

//Only setting the fields needed
export const testUserCredentials: UserCredential = {
  user: {
    providerData: [
      {
        email: 'test@email.com',
      }
    ]
  }
}

测试

  it('should submit registration with form values', fakeAsync(() => {
    spyOn(component, 'signUp').and.callThrough();
    angularFireAuthSignOutSpyObj.createUserWithEmailAndPassword.and.callFake(() => new Promise(
      resolve => {
        resolve(testUserCredentials);
      })
    );

    component.registrationForm.controls.email.setValue('test@email.com');
    component.registrationForm.controls.password.setValue('ValidPass123');
    component.registrationForm.controls.passwordCheck.setValue('ValidPass123');
    expect(component.registrationForm.valid).toBeTruthy();
    debugElement.query(By.css("button")).triggerEventHandler("click", null);
    expect(component.signUp).toHaveBeenCalled();
    expect(component.auth.createUserWithEmailAndPassword)
      .toHaveBeenCalledWith(
        component.registrationForm.controls.email.value,
        component.registrationForm.controls.password.value)
    tick();
    fixture.detectChanges();
    expect(component.appMessage).toEqual('Account created : test@email.com');
  }));

如何触发错误而不是解决

    angularFireAuthSignOutSpyObj.createUserWithEmailAndPassword.and.callFake(() => new Promise(() => {
      throw {message: 'test purpose failure'};
    }));

已更新register.component.ts

  signUp() {
    if (this.registrationForm.valid) {
      let createdEmail: string | null | undefined;
      this.auth.createUserWithEmailAndPassword
      (
        this.registrationForm.get('email')?.value,
        this.registrationForm.get('password')?.value
      )
        .then((userCredential: UserCredential) => {
          userCredential?.user?.providerData?.forEach(userInfo => {
            createdEmail = userInfo?.email;
          })
          this.appMessage = "Account created : " + createdEmail;
        })
        .catch((error) => {
          this.appMessage = "Account creation failed : " + error.message;
        });
    } else {
      this.appMessage = 'Submit logic bypassed, form invalid !'
    }
  }