角单元测试异步与同步问题

时间:2018-11-23 10:47:07

标签: angular unit-testing testing jasmine karma-jasmine

我对角度和茉莉花的单元测试非常陌生,因此我一直在努力使其正确。我正在尝试为登录页面编写一个简单的单元测试。以下是我的代码

import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { LoginComponent } from './login.component';
import {BrowserModule, By} from '@angular/platform-browser';
import {FormsModule, ReactiveFormsModule} from '@angular/forms';
import {Component, DebugElement, Input} from '@angular/core';
import {RouterTestingModule} from '@angular/router/testing';
import {LoaderService} from '@shared/services/loader.service';
import {AuthenticationService} from '@shared/services/authentication.service';
import {HttpClientModule} from '@angular/common/http';
import {CommonService} from '@shared/services/common.service';
import {ExploreService} from '@shared/services/explore.service';
import {TitleService} from '@shared/services/title.service';
import {AppConfig} from '../../app-config.service';
import {ThemeService} from '@shared/services/theme.service';
import {MatDialog, MatFormFieldModule, MatIconModule} from '@angular/material';
import {throwError} from 'rxjs';

@Component({
  selector: 'show-errors',
  template: '<p>Mock Product Settings Component</p>'
})
class MockShowErrorsComponent {
  @Input() public control;
}
describe('LoginComponent', () => {
  let component: LoginComponent;
  let fixture: ComponentFixture<LoginComponent>;
  let authService: any;
  let common: any;
  let explore: any;
  let title: any;
  let config: any;
  let theme: any;
  let dialog: any;
  let debugElement: DebugElement;
  let element: HTMLElement;
  let submitSpy: any;
  beforeEach(async(() => {
    authService = jasmine.createSpyObj('AuthenticationService', [
      'login',
      'logout'
    ]);
    common = jasmine.createSpyObj('commonService', [
      'updateCurrentUrl',
      'isMobile',
    ]);
    explore = jasmine.createSpyObj('exploreService', [
      'slowCalcMessage',
      'cancelSlowMessage',
      'getInventoryTotalSummary',
      'handleError',
      'getMarketData'
    ]);
    title = jasmine.createSpyObj('titleService', [
      'getTitle',
      'setTitle',
      'updateTitle',
      'updateSiteName'
    ]);
    config = jasmine.createSpyObj('AppConfigService', [
      'load',
      'API_ENDPOINT'
    ]);
    theme = jasmine.createSpyObj('ThemeService', [
      'getThemeSettings',
      'generateColorTheme',
    ]);
    dialog = jasmine.createSpyObj('dialog', [
      'open'
      ]);
    TestBed.configureTestingModule({
      imports: [
        BrowserModule,
        FormsModule,
        ReactiveFormsModule,
        RouterTestingModule.withRoutes([]),
        HttpClientModule,
        MatIconModule,
        MatFormFieldModule,
      ],
      declarations: [
        LoginComponent,
      MockShowErrorsComponent
      ],
      providers: [
        LoaderService,
        {provide: AuthenticationService, useValue: authService},
        {provide: CommonService, useValue: common},
        {provide: ExploreService, useValue: explore},
        {provide: TitleService, useValue: title},
        {provide: AppConfig, useValue: config},
        {provide: ThemeService, useValue: theme},
        {provide: MatDialog, useValue: dialog},
      ]
    })
    .compileComponents();
  }));

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

  it('should create', () => {
    expect(component).toBeTruthy();
  });
  it('should fail on wrong credentials', () => {
    const email = element.querySelector('#defaultForm-email') as HTMLInputElement;
    email.value = 'vigneshm@intermx.com';
    email.dispatchEvent(new Event('input'));
    const password = element.querySelector('#defaultForm-pass') as HTMLInputElement;
    password.value = 'agira123';
    password.dispatchEvent(new Event('input'));
    fixture.detectChanges();
    const service = debugElement.injector.get(AuthenticationService);
    submitSpy = spyOn(service, 'logout');
    debugElement.query(By.css('button.login-btn'))
      .triggerEventHandler('click', null);
    fixture.detectChanges();
    expect(submitSpy).toHaveBeenCalled();
  });
});

上面的代码是我对登录屏幕的规格和模拟,我意识到登录页面具有太多的依赖关系,在开始模拟它们时需要简化这些依赖。

真正的问题是,每当我运行此命令时,第二项测试就会失败,并显示TypeError: Cannot set property 'value' of null

我不确定为什么在测试时DOM不可用,我应该等待DOM或angular准备好吗?还是其他?

4 个答案:

答案 0 :(得分:1)

我在Stackblitz中模拟了您的测试。正如您在Stackblitz中看到的那样,测试现在都通过了。这是我为使其正常工作所做的:

  • 首先,我不得不猜测您的组件,因为您没有 包括那些细节。随意分叉我所做的事情, 用您的实际组件详细信息替换它,看看是否可以 重现您的错误。
  • 我必须同时导入MatInputModule和BrowserAnimationsModule TestBed即可正确创建。
  • 我注释了submitSpy = spyOn(service, 'logout')行, 由于您已经传递了间谍,因此无需进行间谍 再次。因此,我也将最后一行更改为 expect(service.login).toHaveBeenCalled();测试间谍 已经通过了。
  • 我在第二个beforeEach周围添加了async() 创建TestBed组件。
  • 我添加了fixture.whenStable()以确保所有内容均已渲染 在测试之前正确进行。

我希望这会有所帮助。

答案 1 :(得分:0)

使用vsc并安装simon测试插件。现在,右键单击您要为其编写单元测试的组件。选择选项生成测试。它将生成一个规范文件,其中所有相关性都写在那上面。现在,您将文件的测试用例粘贴到文件上方。

答案 2 :(得分:0)

认为您的问题是const email = element.querySelector('#defaultForm-email') as HTMLInputElement;以及所有此类element.querySelector

通常,您应该使用fixture.nativeElement.querySelector(className);id。由于TestBed通常会为您创建一个运行测试的安全环境。

您通常应该使用一些东西

const password: HTMLInputElement = fixture.nativeElement.querySelector('#defaultForm-pass');
 password.setValue('value');

答案 3 :(得分:0)

我已修复错误,问题出在模板上。我有一个全局* ngIf语句,用于检查是否存在名为主题设置的变量

<div *ngIf="themeSettings"></div>

在测试中,我忘记了对themeSettings变量进行存根,从而导致永远无法创建测试所需的DOM。在为themeSettings添加存根值后,它又开始工作。

要点:如果模板中包含ngIf,请确保将其存根并在测试中为这些值设置适当的值,并确保在发布之前正确创建了DOM。