【发布时间】: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