【问题标题】:Angular Jest async test seems to overflow results from one test to anotherAngular Jest 异步测试似乎将结果从一个测试溢出到另一个
【发布时间】:2020-11-14 08:47:49
【问题描述】:

在 Angular 中,使用 Jest 我们有 2 个测试来测试组件类上的方法:

  describe('checkEmailStatus', () => {
    it('set VERIFIED page design when email verification succeeds', async () => {
      jest.spyOn(authService, 'checkEmailVerification');
      await expect(component.checkEmailStatus()).resolves.toEqual(undefined);
      expect(authService.checkEmailVerification).toBeCalledTimes(1);
      expect(component.pageDesign.key).toBe('verified');
    });

    it('set ERROR page design when email verification fails', async () => {
      const checkEmail = jest.spyOn(authService, 'checkEmailVerification');
      checkEmail.mockImplementation(() => {
        return Promise.reject(false);
      });
      await expect(component.checkEmailStatus()).resolves.toEqual(undefined);
      expect(authService.checkEmailVerification).toBeCalledTimes(1);
      expect(component.pageDesign.key).toBe('error');
    });
  });

这些测试已经运行了一个月。这个组件没有任何改变,我们也没有改变 Jest 版本(25.2.7)但现在第二个测试抱怨该方法被调用了 3 次。

如果我注释掉第一个测试,则第二个测试通过。

似乎第一个测试没有正确拆除 - 我需要做些什么来强制这样做吗? (我尝试使用 done() 回调,但没有任何区别)

更新

这是正在测试的方法:

  async checkEmailStatus(): Promise<void> {
    this.isLoading = true;
    try {
      await this.authService.checkEmailVerification('');
      this.setPageDesign('verified');
      this.isLoading = false;
    } catch (error) {
      this.setPageDesign('error');
      this.isLoading = false;
    }
  }

这是存根的 authService:

import {Observable, BehaviorSubject, of} from 'rxjs';
import {switchMap} from 'rxjs/operators';
import {mockUsers} from '../../../../mocks/user.mock';

// tslint:disable-next-line: completed-docs
function initStub() {
  const userId$ = new BehaviorSubject<string>(null);

  return {
    userId$,
    checkEmailVerification(): Promise<boolean> {
      return Promise.resolve(true);
    }
  };
}

export const authServiceStub = initStub();

更新 2

这是完整的测试文件:

import {AuthService} from 'src/app/shared/services/auth.service';
import {async, ComponentFixture, TestBed} from '@angular/core/testing';

import {VerifyEmailComponent} from './verify-email.component';
import {SharedModule} from '../shared/shared.module';
import {getTranslocoModule} from '../transloco-testing.module';
import {BrowserAnimationsModule} from '@angular/platform-browser/animations';
import {AngularFireModule} from '@angular/fire';
import {environment} from 'src/environments/environment';
import {routerStub} from '../test/helpers/router.stub';
import {authServiceStub} from '../test/helpers/auth.service.stub';

fdescribe('VerifyEmailComponent', () => {
  let component: VerifyEmailComponent;
  let fixture: ComponentFixture<VerifyEmailComponent>;
  let authService: AuthService;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [VerifyEmailComponent],
      imports: [
        SharedModule,
        getTranslocoModule({}),
        BrowserAnimationsModule,
        AngularFireModule.initializeApp(environment.firebase)
      ],
      providers: [routerStub, {provide: AuthService, useValue: authServiceStub}]
    }).compileComponents();
    authService = TestBed.inject(AuthService);
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(VerifyEmailComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  afterEach(() => {
    jest.clearAllMocks();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  describe('setPageDesign', () => {
    it('should set the correct design for VERIFY', () => {
      component.setPageDesign('verify');
      expect(component.pageDesign.key).toBe('verify');
    });

    it('should set the correct design for VERIFIED', () => {
      component.setPageDesign('verified');
      expect(component.pageDesign.key).toBe('verified');
    });

    it('should set the correct design for ERROR', () => {
      component.setPageDesign('error');
      expect(component.pageDesign.key).toBe('error');
    });

    it('should set the ERROR design for unknown status values', () => {
      component.setPageDesign('');
      expect(component.pageDesign.key).toBe('error');
    });
  });

  describe('checkEmailStatus', () => {
    it('set VERIFIED page design when email verification succeeds', async () => {
      jest.spyOn(authService, 'checkEmailVerification');
      await expect(component.checkEmailStatus()).resolves.toEqual(undefined);
      expect(authService.checkEmailVerification).toBeCalledTimes(1);
      expect(component.pageDesign.key).toBe('verified');
    });

    it('set ERROR page design when email verification fails', async () => {
      const checkEmail = jest.spyOn(authService, 'checkEmailVerification');
      checkEmail.mockImplementation(() => {
        return Promise.reject(false);
      });
      await expect(component.checkEmailStatus()).resolves.toEqual(undefined);
      fixture.detectChanges();

      expect(authService.checkEmailVerification).toBeCalledTimes(1);
      expect(component.pageDesign.key).toBe('error');
    });
  });

  describe('onClickContinue', () => {
    // TODO: implement 2 tests for if/else cases of the button
    return undefined;
  });
});

这是组件代码:

import {TranslocoService, TRANSLOCO_SCOPE} from '@ngneat/transloco';
import {Component, OnInit} from '@angular/core';
import {AuthService} from '../shared/services/auth.service';
import {Router} from '@angular/router';

// define static data to be used only by this component
interface PageDesign {
  icon: string;
  key: string;
}
const pageDesigns: PageDesign[] = [
  {
    icon: 'email-verified',
    key: 'verify'
  },
  {
    icon: 'email-verified',
    key: 'verified'
  },
  {
    icon: 'email-expired',
    key: 'error'
  }
];

@Component({
  selector: 'wn-verify-email',
  templateUrl: './verify-email.component.html',
  styleUrls: ['./verify-email.component.scss'],
  providers: [{provide: TRANSLOCO_SCOPE, useValue: 'verifyEmail'}]
})
export class VerifyEmailComponent implements OnInit {
  isLoading: boolean = false;
  pageDesign: PageDesign;

  constructor(
    public translocoService: TranslocoService,
    private authService: AuthService,
    private router: Router
  ) {}

  /**
   * Init
   */
  ngOnInit(): void {
    this.setPageDesign('verify');
    this.checkEmailStatus();
  }

  /**
   * Affects the current email data
   */
  setPageDesign(status: string): any {
    this.pageDesign = pageDesigns.find(
      emailDesign => emailDesign.key === status
    );
    if (!this.pageDesign)
      this.pageDesign = pageDesigns.find(
        emailDesign => emailDesign.key === 'error'
      );
  }

  /**
   * Check whether email address is verified
   */
  async checkEmailStatus(): Promise<void> {
    this.isLoading = true;
    try {
      await this.authService.checkEmailVerification('');
      this.setPageDesign('verified');
      this.isLoading = false;
    } catch (error) {
      this.setPageDesign('error');
      this.isLoading = false;
    }
  }

  /**
   * Click handler for the continue navigation button
   */
  onClickContinue(status: string) {
    if (status === 'verified')
      // TODO: use the continueURL from params and navigate to that
      console.error('continue url needed');
    else this.router.navigate(['/']);
  }
}

【问题讨论】:

  • 能否请您添加完整的测试文件和组件代码的sn-p?
  • @PrinceIsNinja 我已经添加了文件。我还添加了您建议的答案,正如您在文件中看到的那样......但现在它抱怨它被调用了 2 次而不是 3 次,所以略有改进!
  • 您能否像在第二个测试用例中一样,在第一个测试用例中提供 mockImplementation。我假设它正在执行需要时间的实际操作。否则我们需要在测试用例中使用调试器对其进行调试............并且还在 afterEach() 中使用 jest.restoreAllMocks()
  • 第一个测试用例肯定是调用了stub方法,它返回true。如果我在第一个测试中做一个 mockImplementation,解析为 true,结果是一样的(收到 2 个调用而不是 1 个)

标签: angular asynchronous testing jestjs


【解决方案1】:

如果您想重置间谍计数,请在测试用例内或 afterEach 块内重置模拟。它将重置 spy 调用的次数。

afterEach(() => {    
  jest.clearAllMocks();
});

您可以在此处阅读更多信息:https://jestjs.io/docs/en/jest-object#jestclearallmocks

希望对你有帮助!

有很多链接会对您有所帮助:

Use of spyOn and read its NOTE very carefully.

Use of mockRestore

【讨论】:

  • 感谢您的帮助,但如果我在第二次测试开始时输入jest.clearAllMocks();,它就可以了!
  • 很好,不知道为什么它在 afterEach 中不起作用。 afterEach 是每个测试用例之后的方法调用。乐于助人!
猜你喜欢
  • 2020-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-11-24
  • 1970-01-01
  • 1970-01-01
  • 2021-12-06
  • 1970-01-01
相关资源
最近更新 更多