使用 Angular 进行单元测试:如何测试 parent 到 child 的更改

Unit testing with Angular: how to test changes on parent to child

我正在使用 Jasmine 对 Angular 中的组件进行单元测试。 我的测试

it('contacts should be passed to child component', fakeAsync(() => {
    const newContact: Contact = {
        id: 1,
        name: 'Jason Pipemaker'
    };
    const contactsList: Array<Contact> = [newContact];
    contactsComponent.contacts = contactsList;//change on parent component
    tick()
    fixture.detectChanges();
    fixture.whenStable().then(() => {
        expect(childComponents()[0].contacts).toEqual(contactsComponent.contacts);//want to check if changed on child also
    })
}));

我想测试 parent 中的更改是否会反映在 child 中,就像在真实场景中发生的那样。

我已经测试过当一切开始时值是相等的,但我想测试当一个人改变 parent 值时的场景,并且应该自动反映在 child.

it('contacts should be the same', () => {
   expect(childComponents([0].contacts)
      .toEqual(contactsComponent.contacts);
});

错误:

Expected $.length = 0 to equal 1. Expected $[0] = undefined to equal Object({ id: 1, name: 'Jason Pipemaker' }).

我的解读:不是等更新测试

也许连单元测试都算不上?由于我要测试绑定,所以可能是集成测试。

到目前为止的建议

建议 1:我会尝试 contactsComponent.contacts = JSON.parse(JSON.stringify(contactsList))

评论:谢谢,它不起作用。看到我一开始就测试过了:一切通过。问题是。为什么它在 beginning/launching 有效,但在开始后却无效?我想这是一个以某种方式确保在测试之前一切都完成的问题:一个异步问题,而不是 JSON.stringify。我不知道如何用代码来做到这一点!简单来说:我正在 parent 上更改某些内容,并希望确保 child 收到更改,就像在真实场景中发生的那样。感谢您的参与! 我的 HTML:

<app-contact-list [contacts]=contacts></app-contact-list>

PS。 app-contact-list 是 child:

export class ContactListComponent implements OnInit {
  .......
  @Input('contacts') contacts: Contact[];//the input for the component

建议2:我认为你应该单独测试组件。

评论: 关于模拟 child,我已经在使用 ng-mock

进行模拟
beforeEach(async(() => {
   TestBed.configureTestingModule({
      declarations: [ContactsComponent, 
         MockComponent(ContactListComponent)],
   }).compileComponents();
}));

缺点:一些组织不允许安装这些软件包,这对我来说是私人课程,因此,应该按照建议手动模拟。

建议 3:您可以利用 async/await 语法避免它。

评论:试过,但没有用,和以前一样的问题。似乎代码在测试之前正在执行。我在玩笑话时遇到了同样的问题,学到了一个以前从未发生过的技巧。我想我遗漏了类似的东西,确保测试在测试之前等待最终修改的东西。

代码:一些人建议提供代码。 开始了:https://github.com/JorgeGuerraPires/testing-angular-applications/tree/master/website/src/app/contacts

也许有人可以按照建议制作 StackBlitz!

最终解

为了关闭这个问题,我决定在一个简单的案例中进行测试,因为这样测试的核心思想将是唯一的关注点。查看完整的简单应用程序 here before testing. And see the final testing file here

我从两个答案中得出了见解,并接受了对我帮助最大的那个,甚至评论也有帮助。

谢谢大家!

我认为你应该单独测试组件。在您的 ContactListComponent 中,测试它是否包含正确的联系人。类似于下面的简单测试。测试 child 是一个完全不同的测试,所以模拟 child 组件。

  it('have the correct contacts', () => {
    expect(component.contacts[0].id).toBe(1);
    expect(component.contacts[0].name).toBe('value');
  });

简单模拟示例:

@Component({
  selector: 'child-component'
  template: ''
})
class TestChildComponent {
  @Input() contact;
}

现在,您想测试一下 ChildComponent。这可以通过打桩 parent 并测试 child 是否反映了正确的数据来完成。

示例:

@Component({
  template: '<child-component [contact]="contact"></child-component>'
})
class TestHostComponent {
  contact = new Contact();
  @ViewChild(ChildComponent) component: ChildComponent;
}

describe('ChildComponent', () => {
  let component: ChildComponent;
  let host: TestHostComponent;
  let fixture: ComponentFixture<TestHostComponent>;


  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ChildComponent, TestHostComponent],
      ...
    }).compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(TestHostComponent);
    loader = TestbedHarnessEnvironment.loader(fixture);
    fixture.detectChanges();

    host = fixture.componentInstance;
    component = host.component;
    fixture.detectChanges();
  });

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

  it('should be able to present a contact', () => {
    host.contact = new Contact('otherValue');
    expect(SomeQuery.value).toBe('otherValue')
  }); 

});

编辑:

您可以添加 CUSTOM_ELEMENTS_SCHEMA.

而不是模拟 child 组件
TestBed.configureTestingModule({
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
  ...
});

这将防止 angular 在找不到组件时引发错误。谨慎使用,因为这可能会给人造成一切都很好的错误印象。

我喜欢这种测试绑定的方法,并且在我的代码中也这样做。提供的测试几乎是正确的。只是异步性的一个小问题。 fixture.whenStable().then( 中的代码将在 karma 认为测试完成后执行。您可以利用 async/await 语法来避免它。

it('contacts should be passed to child component', aynsc () => {
    const newContact: Contact = {
        id: 1,
        name: 'Jason Pipemaker'
    };
    const contactsList: Array<Contact> = [newContact];
    contactsComponent.contacts = contactsList;//set on parent component

    fixture.detectChanges();
    await fixture.whenStable();

    expect(childComponents()[0].contacts)
       .toEqual(contactsComponent.contacts);//want to check if changed on child also
}));

it('child component should receive data when data in parent is changed', aynsc () => {
    const firstContact: Contact = {
        id: 1,
        name: 'Jason Pipemaker'
    };
    const contactsList: Array<Contact> = [firstContact];
    contactsComponent.contacts = contactsList;//set on parent component
    fixture.detectChanges();
    await fixture.whenStable();
    const secondContact: Contact = {
        id: 1,
        name: 'Jason Pipemaker'
    };

    const contactsList2: Array<Contact> = [firstContact, secondContact];
    contactsComponent.contacts = contactsList2;//change on parent component
    fixture.detectChanges();
    await fixture.whenStable();

    expect(childComponents()[0].contacts)
       .toEqual(contactsComponent.contacts);//want to check if changed on child also
}));