首页 > 解决方案 > Angular Jest 异步测试似乎将结果从一个测试溢出到另一个

问题描述

在 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(['/']);
  }
}

标签: angularasynchronoustestingjestjs

解决方案


If you want to reset the spy count, either reset the mocks inside the test case or inside the afterEach block. It will reset the number of times spy called.

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

You ca read more about it here: https://jestjs.io/docs/en/jest-object#jestclearallmocks

Hope it will help you!

There are number of links that will be helpful for you:

Use of spyOn and read its NOTE very carefully.

Use of mockRestore


推荐阅读