【问题标题】:Test mocked service then/catch on function - Angular/Karma然后测试模拟服务/捕获功能 - Angular/Karma
【发布时间】:2021-11-30 03:10:52
【问题描述】:

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

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

angularFireAuthSignOutSpyObj 是来自组件的 this.auth 的间谍(Firebase 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 !'
    }
  }

我正在使用业力测试来测试这个组件功能

  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 被注释掉,因为它返回一个 Error: Expected undefined to equal 'Account created !'。 这是因为即使 this.auth.createUserWithEmailAndPassword 是在模拟服务 angularFireAuthSignOutSpyObj 中定义的,并且使用 2 个预期参数正确调用,我无法控制 thencatch 定义的函数。

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

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

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

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

非常感谢!

大卫

【问题讨论】:

    标签: angular typescript unit-testing testing karma-jasmine


    【解决方案1】:

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

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

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

    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

    【讨论】:

    • 谢谢,我会试试的!这是我创建间谍的代码``` angularFireAuthSignOutSpyObj = jasmine.createSpyObj('AngularFireAuth', ['createUserWithEmailAndPassword']); ```我实际上正在寻找测试这个组件的最佳方法/实践。我愿意改变技术。关于 Observables 我应该寻找什么?
    • 我试过这段代码` let resolveFunction: Function;让拒绝函数:函数; angularFireAuthSignOutSpyObj.createUserWithEmailAndPassword.and.returnValue(new Promise((resolve, reject) => { resolveFunction = resolve; rejectFunction = reject; })); ... resolveFunction('成功'); ` 不幸的是,我得到了同样的结果。请注意,我收到此错误在分配之前使用了变量“resolveFunction”。
    • 如果承诺从未被听过,或者如果间谍设置不正确,则可能会发生这种情况。我不明白angularFireAuthSignOutSpyObj 是什么,但你的语法看起来不适合间谍;您必须对间谍执行“and.returnValue()”;不是间谍中的对象。
    【解决方案2】:

    嘿,我只是想发布一个更新,因为我设法做到了我需要的。感谢@JeffryHouser 的提醒。

    所以基本上我的组件最初期望从查询中得到一个 Promise。如果结果恢复正常(UserCredentials),我们只需使用成功消息更新 appMessage 字符串。如果没有(捕获),我们将返回错误 message

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

    • 使用 fakeAsync() 将测试设置为异步
    • 监视用户 click() 使用的每个函数
    • 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 !'
        }
      }
    

    【讨论】:

      猜你喜欢
      • 2018-01-31
      • 1970-01-01
      • 2017-08-15
      • 1970-01-01
      • 1970-01-01
      • 2020-08-19
      • 1970-01-01
      • 2017-10-27
      • 1970-01-01
      相关资源
      最近更新 更多