首页 > 解决方案 > 如何使用 auth.service.ts 为 login.component.ts 编写单元测试?

问题描述

我有我想测试的带有 submit() 函数的 login.component 。我怎样才能做到这一点?我读了很多文章,但我不明白如何在 login.component 的 submit() 方法中调用我的 auth.service

登录组件.ts

export class LoginComponent implements OnInit{
  form: FormGroup;
  error: string;

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

  ngOnInit(): void {
    this.form = new FormGroup({
      email: new FormControl('', [Validators.required, Validators.email]),
      password: new FormControl('', [Validators.required, Validators.minLength(6)])
    });
  }


  submit(): void {
    if (this.form.invalid) {
      return;
    }
    const user: User = {
      email: this.form.value.email,
      password: this.form.value.password,
    };

    this.authService.signIn(user)
      .then(() => this.router.navigate(['/']))
      .catch(err => this.error = err.message);
  }
.....
}

auth.service.ts 这是我调用的 singIn() 方法

@Injectable(
  {providedIn: 'root'}
)
export class AuthService {

  user: User;

  constructor(private angularFireAuth: AngularFireAuth,
              private httpClient: HttpClient) {
  }

  signIn(user: User): Promise<any> {
    return this.angularFireAuth.signInWithEmailAndPassword(user.email, user.password)
    .then(result => {
        return this.user = {
          email: result.user.email,
          uid: result.user.uid,
        };
      });
  }
....
}

现在我有这样的“规范”文件来测试 login.component.spec.ts

describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  class MockAuthService{
    user: User;
    signIn(user: User): Promise<any> {
      return new Promise(resolve => resolve(this.user = {...user}));
    }
  }

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [LoginComponent],
      imports: [RouterTestingModule, FormsModule, ReactiveFormsModule],
      providers: [{provide: AuthService, useClass: MockAuthService}],
      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
    });
      .compileComponents();
  });

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

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

  it('should create form with 2 controls', () => {
    expect(component.form.contains('email')).toBeTruthy();
    expect(component.form.contains('password')).toBeTruthy();
  });

  it('should render error message', () => {
    const errorMessage = 'Error';
    component.error = errorMessage;
    fixture.detectChanges();
    const debug = fixture.debugElement.query(By.css('.form__error'));
    const el: HTMLElement = debug.nativeElement.textContent;
    expect(el).toContain(errorMessage);
  });

  it('component initial state', () => {
    expect(component.submit).toBeDefined();
    expect(component.googleAuth).toBeDefined();
    expect(component.facebookAuth).toBeDefined();
    expect(component.githubAuth).toBeDefined();
    expect(component.form.invalid).toBeTruthy();
    expect(component.error).toBeUndefined();
  });

  it('should be true when invalid form', () => {
    component.form.controls['email'].setValue('check.point@');
    component.form.controls['password'].setValue('231');
    expect(component.form.invalid).toBeTruthy();
  });
});

标签: angularunit-testingjasmine

解决方案


我想你快到了。

// get rid of class MockAuthService, we will use spyObj instead
let mockAuthService: jasmine.SpyObj<AuthService>;
let router: Router;
beforeEach(async () => {
    // the first argument is string is an identifier (optional) and the second argument is an array of strings of the public methods you would like to mock
    mockAuthService = jasmine.createSpyObj<AuthService>('AuthService', ['signIn']);
    await TestBed.configureTestingModule({
      declarations: [LoginComponent],
      imports: [RouterTestingModule, FormsModule, ReactiveFormsModule],
      // change below line
      providers: [{provide: AuthService, useValue: mockAuthService }],
      schemas: [NO_ERRORS_SCHEMA, CUSTOM_ELEMENTS_SCHEMA],
    });
      .compileComponents();
  });

beforeEach(() => {
    router = TestBed.inject(router); // inject is get if on version 9 and below of Angular
    // spy on the navigate method so it doesn't actually want to navigate
    spyOn(router, 'navigate');
    fixture = TestBed.createComponent(LoginComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

...
it('should navigate on submit', async () => {
   // set these values so the form is valid and the method does not return early
   component.form['email'].setValue('test@test.com');
   component.form['password'].setValue('1234567');
   
   // for our testing we don't care what it returns, as long as it is a promise
   mockAuthService.signIn.and.returnValue(Promise.resolve(undefined));
   component.submit();
   // wait for all pending promises to resolve
   await fixture.whenStable();
   expect(router.navigate).toHaveBeenCalledWith(['/]);
});

it('should set error on error', async () => {
   // set these values so the form is valid and the method does not return early
   component.form['email'].setValue('test@test.com');
   component.form['password'].setValue('1234567');
   
   // for our testing we don't care what it returns, as long as it is a promise
   mockAuthService.signIn.and.returnValue(Promise.reject({ message: 'error' }));
   component.submit();
   // wait for all pending promises to resolve
   await fixture.whenStable();
   expect(component.error).toBe('error');
});

推荐阅读