简体   繁体   English

然后在 function 上测试模拟服务/捕获 - Angular/Karma

[英]Test mocked service then/catch on function - Angular/Karma

I have a coverage issue with my Angular/Karma tests.我的 Angular/Karma 测试存在覆盖问题。

I created a component that has a signUp() function我创建了一个具有 signUp() function 的组件

angularFireAuthSignOutSpyObj is a spy of this.auth from the component (Firebase Auth) 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 !'
    }
  }

I'm testing this component function with a karma test as is我正在按原样使用业力测试测试这个组件 function

  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 !');
  });

As you can notice the last expect is commented out as it returns an Error: Expected undefined to equal 'Account created.'.正如您所注意到的,最后一个expect被注释掉,因为它返回错误:Expected undefined to equal 'Account created.'。 This is because even though this.auth.createUserWithEmailAndPassword is defined in the mocked service angularFireAuthSignOutSpyObj , and is correctly called with the 2 expected arguments, I have no control over the then and catch functions that are defined.这是因为即使this.auth.createUserWithEmailAndPassword是在模拟服务angularFireAuthSignOutSpyObj中定义的,并且使用 2 个预期的 arguments 正确调用,我也无法控制定义的thencatch函数。

They are defined so it won't trigger an error when trying to access it in the signUp() function. But what I would like to do is trigger the then(() =>...) and the catch(() =>...) so I can test/check that the app.message was correctly updated.它们被定义为因此在signUp() function 中尝试访问它时不会触发错误。但我想做的是触发 then(() =>...) 和 catch(() = >...) 这样我就可以测试/检查 app.message 是否已正确更新。

All the exceptions work until the last one.所有异常都有效,直到最后一个。 I feel like that I need to modify something in my createUserWithEmailAndPassword.and.returnValue to probably return something that triggers the then or the catch.我觉得我需要修改createUserWithEmailAndPassword.and.returnValue中的某些内容,以便可能返回触发 then 或 catch 的内容。

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

Anyone has an idea on how I could test the actual auth.createUserWithEmailAndPassword result behaviour of my component?任何人都知道如何测试我的组件的实际 auth.createUserWithEmailAndPassword 结果行为?

Thanks very much !非常感谢 !

David大卫

I didn't see the code where you created the spy.我没有看到你创建间谍的代码。 Also a bit weird you're using promises instead of Observables.您使用 promises 而不是 Observables 也有点奇怪。 But, I'd look into spying on the method--not the class, and returning a promise that you control:但是,我会研究监视该方法——而不是 class,并返回您控制的 promise:

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

Now from your tests you can control when the promise is rejected or resolved just by calling those functions:现在,通过您的测试,您可以通过调用这些函数来控制 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 有关手动创建承诺的更多信息

Hey just wanted to post an update as I managed to do exactly what I needed.嘿,我只是想发布更新,因为我设法完全按照我的需要做了。 Thanks to @JeffryHouser for the heads up.感谢@JeffryHouser 的提醒。

So basically my component initially expects a Promise from the query.所以基本上我的组件最初期望来自查询的Promise If the results come back normal (UserCredentials) we simply update the appMessage string with a successful message.如果结果恢复正常(UserCredentials),我们只需使用成功消息更新 appMessage 字符串。 If not (catch) we return the error message .如果没有(捕获)我们返回错误信息

Those are the changes I made on the test side in order to simulate the resolve (normal result of the promise, and below how to trigger the catch)这些是我在测试端为模拟解析所做的更改(promise 的正常结果,下面是如何触发捕获)

  • Set the test as async with fakeAsync()使用fakeAsync()将测试设置为异步
  • Spy on every function that are used from the user click()监视用户 click() 使用的每个 function
  • Specified a return as Promise for the angularFireAuthSignOutSpyObj.createUserWithEmailAndPassword functionangularFireAuthSignOutSpyObj.createUserWithEmailAndPassword function 指定返回值 Promise
  • Simulate the async flow with tick()使用tick()模拟异步流程
  • Detect the changes following the end of promise flow with fixture.detectChanges()使用 fixture.detectChanges ()检测 promise 流程结束后的变化

The appMessage item is correctly updated following the process appMessage 项目按照流程正确更新

Here is the code !这是代码

Spy declaration间谍宣言

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

User credentials item用户凭据项目

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

Test测试

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

How to trigger the error instead of the resolve如何触发错误而不是解决

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

Updated register.component.ts更新了 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 !'
    }
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM