【问题标题】:How to mock MatChipInput when testing with Jasmine使用 Jasmine 进行测试时如何模拟 MatChipInput
【发布时间】:2020-03-05 07:08:57
【问题描述】:

我已经设置了一个stackblitz 来基本显示问题所在。

基本上,当我尝试在包含 MatChipList 的 MatFormField 上触发事件时,我收到了

的错误
 Cannot read property 'stateChanges' of undefined at MatChipInput._onInput

我尝试用 MatInput 的替代模拟来覆盖 MatChip 模块。我也尝试过覆盖指令。

HTML

 <h1>Welcome to app!!</h1>

 <div>
  <mat-form-field>
   <mat-chip-list #chipList>
    <mat-chip *ngFor="let contrib of contributors; let idx=index;" [removable]="removable" (removed)="removeContributor(idx)">
     {{contrib.fullName}}
    </mat-chip>
    <input  id="contributor-input"
        placeholder="contributor-input"
        #contributorInput
        [formControl]="contributorCtrl"
        [matAutocomplete]="auto"
        [matChipInputFor]="chipList"
        [matChipInputSeparatorKeyCodes]="separatorKeysCodes"
        [matChipInputAddOnBlur]="addOnBlur">
  </mat-chip-list>
 </mat-form-field>
</div>

TS

import { Component, Input } from '@angular/core';
import { COMMA, ENTER } from '@angular/cdk/keycodes';
import { FormControl } from '@angular/forms';
import { Observable } from 'rxjs';
import { map, startWith } from 'rxjs/operators';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {

contributors = [{fullName: 'foo bar'}];
removable = true;
addOnBlur = false;
separatorKeysCodes: number[] = [
    ENTER,
    COMMA,
];

contributorCtrl = new FormControl();

filteredPeople: Observable<Array<any>>;

@Input() peopleArr = [];

constructor() {
   this.filteredPeople = this.contributorCtrl.valueChanges.pipe(startWith(''), map((value: any) => 
this.searchPeople(value)));
 }

 searchPeople(searchString: string) {
    const filterValue = String(searchString).toLowerCase();
    const result = this.peopleArr.filter((option) => option.fullName.toLowerCase().includes(filterValue));
    return result;
  }
}

规格

import { RouterTestingModule } from '@angular/router/testing';
import { AppComponent } from './app.component';
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { 
  BrowserDynamicTestingModule, 
  platformBrowserDynamicTesting 
} from '@angular/platform-browser-dynamic/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {  MatFormFieldModule, 
      MatAutocompleteModule, 
      MatInputModule,
      MatChipsModule } from '@angular/material';
import { FormsModule, ReactiveFormsModule} from '@angular/forms';

describe('AppComponent', () => {

  const mockPeopleArray = [
    { personId: 1,
      email: 'foo1@bar.com',
      department: 'fake1',
      username: 'foo1',
      fullName: 'Foo Johnson'
     },
     { personId: 2,
      email: 'foo2@bar.com',
      department: 'fake1',
      username: 'foo2',
      fullName: 'John Fooson'
     },
     { personId: 3,
      email: 'foo1@bar.com',
      department: 'fake2',
      username: 'foo3',
      fullName: 'Mary Smith'
     }
 ];


 let app: AppComponent;
 let fixture: ComponentFixture<AppComponent>;
 let nativeElement: HTMLElement;

 beforeAll( ()=> {
  TestBed.initTestEnvironment(BrowserDynamicTestingModule, 
  platformBrowserDynamicTesting());
  });
  beforeEach(
   async(() => {
     TestBed.configureTestingModule({
       imports: [
       RouterTestingModule,
       MatFormFieldModule,
       FormsModule,
       ReactiveFormsModule,
       MatAutocompleteModule,
       MatChipsModule,
       MatInputModule,
       NoopAnimationsModule
       ],
       declarations: [AppComponent]
     }).compileComponents();

   fixture = TestBed.createComponent(AppComponent);
   app = fixture.debugElement.componentInstance;
   nativeElement = fixture.nativeElement;
  })
 );
 it(
 'should render title \'Welcome to app!!\' in a h1 tag', async(() => {
  fixture.detectChanges();
  expect(nativeElement.querySelector('h1').textContent).toContain('Welcome to app!!');
})
);

it('searchPeople should trigger and filter', (done) => {
  app.peopleArr = mockPeopleArray;

  const expected = [
    { personId: 3,
      email: 'foo1@bar.com',
      department: 'fake2',
      username: 'foo3',
      fullName: 'Mary Smith'
     }
  ];

  const myInput = <HTMLInputElement> 
  nativeElement.querySelector('#contributor-input');
  expect(myInput).not.toBeNull();
  myInput.value = 'Mar';
  spyOn(app, 'searchPeople').and.callThrough();
  myInput.dispatchEvent(new Event('input'));
    fixture.detectChanges();
    fixture.whenStable().then(() => {
        const myDiv = nativeElement.querySelector('#contrib-div');
        expect(app.searchPeople).toHaveBeenCalledWith('mar');
        app.filteredPeople.subscribe(result => 
        expect(result).toEqual(<any>expected));
        done();
    });
  });
 });

【问题讨论】:

  • 您好。请尽量避免在帖子中添加(或重新添加)闲聊材料。志愿编辑会尝试在此处编辑/策划材料以符合技术写作标准,以便为未来的读者提供清晰简洁的帖子。
  • 为了说明什么是闲聊材料,我有时会提出以下建议:请注意,我们更喜欢这里的技术写作风格。我们轻轻地劝阻问候,希望你能帮助,谢谢,提前感谢,感谢信,问候,亲切的问候,签名,请你能帮助,聊天材料和缩写 txtspk,恳求,你多久了被卡住、投票建议、元评论等。只需解释您的问题,并展示您尝试过的内容、预期的内容以及实际发生的情况。

标签: angular unit-testing jasmine angular-material


【解决方案1】:

你得到:

无法读取未定义的属性“stateChanges” MatChipInput._onInput

因为 Angular 在触发时还没有完成绑定myInput.dispatchEvent(new Event('input'))

要解决这个问题,您应该首先调用fixture.detectChanges,以便 Angular 执行数据绑定。

那么你不需要让这个测试异步,因为所有的动作都是同步执行的。

现在关于您的searchPeople 方法。自从您使用startWith('') 以初始值开始订阅后,它将被调用两次:

this.contributorCtrl.valueChanges.pipe(startWith('')

所以你需要跳过第一次调用并在触发input事件后检查调用结果。

app.filteredPeople.pipe(skip(1)).subscribe(result => {
  ...
});

spyOn(app, "searchPeople").and.callThrough();

myInput.dispatchEvent(new Event("input"));
expect(app.searchPeople).toHaveBeenCalledWith("Mar");

整个测试代码:

it("searchPeople should trigger and filter", () => {
  app.peopleArr = mockPeopleArray;

  const expected = [
    {
      personId: 3,
      email: "foo1@bar.com",
      department: "fake2",
      username: "foo3",
      fullName: "Mary Smith"
    }
  ];

  fixture.detectChanges();
  const myInput = nativeElement.querySelector<HTMLInputElement>(
    "#contributor-input"
  );
  expect(myInput).not.toBeNull();
  myInput.value = "Mar";

  app.filteredPeople.pipe(skip(1)).subscribe(result => 
    expect(result).toEqual(expected);
  );

  spyOn(app, "searchPeople").and.callThrough();

  myInput.dispatchEvent(new Event("input"));
  expect(app.searchPeople).toHaveBeenCalledWith("Mar");
}); 

Forked Stackblitz

【讨论】:

  • 太棒了。谢谢!并感谢您的详细解释。一个小提示,在 Jasmine toBe 中会检测到这两个对象总是不同的。 toEqual 似乎做了不同类型的比较。 expect(result).toEqual(expected) 函数符合预期。
  • @E.Maggini 谢谢你的注意:)
猜你喜欢
  • 2013-10-16
  • 2017-06-18
  • 1970-01-01
  • 1970-01-01
  • 2015-09-18
  • 1970-01-01
  • 1970-01-01
  • 2016-04-04
相关资源
最近更新 更多