Reputation: 632
I am unit testing a component in Angular, using Jasmine. My test
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
})
}));
I want to test if a change in the parent will reflect on the child, like happens in a real scenario.
I have tested that the values are equal when everything starts, but I want to test the scenario when one changes the parent value, and that should automatically reflect on the child.
it('contacts should be the same', () => {
expect(childComponents([0].contacts)
.toEqual(contactsComponent.contacts);
});
Error:
Expected $.length = 0 to equal 1. Expected $[0] = undefined to equal Object({ id: 1, name: 'Jason Pipemaker' }).
My interpretation: it is not waiting for the update to test.
Maybe it is not even a unit test? Since I want to test the binding, maybe it is an integration test.
Suggestions so far
Suggestion 1: I would try with contactsComponent.contacts = JSON.parse(JSON.stringify(contactsList))
comments: Thanks, it does not work. See that I have already tested at the beginning: everything passes. The question would be. Why did it work at the beginning/launching but not after started? I guess this is a problem of somehow making sure everything finishes before testing: an async issue, not JSON.stringify. I have no idea how to make that in code terms! In simple terms: I am changing something on the parent, and want to make sure the child receives the change, like it happens on real scenarios. Thanks for your participation! 🤝👊💪 My HTML:
<app-contact-list [contacts]=contacts></app-contact-list>
PS. app-contact-list is the child:
export class ContactListComponent implements OnInit {
.......
@Input('contacts') contacts: Contact[];//the input for the component
Suggestion 2: I think you should test the component individually.
Comments: Regarding mocking the child, I am already mocking using ng-mock
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ContactsComponent,
MockComponent(ContactListComponent)],
}).compileComponents();
}));
Drawback: it came to me as private lessons that some organizations do not allow installing those packages, thus, one should mock manually, as suggested.
Suggestion 3: You can avoid it by leveraging the async/await syntax.
Comments: tried, but did not work, same problem as before. it seems the code is executing before test. I face the same problem with Jest, learnt a trick, that that never happened before. I guess I am missing something like that, something that makes sure the test waits for the final modifications before testing.
Codes: some suggested making the codes available. Here it goes: https://github.com/JorgeGuerraPires/testing-angular-applications/tree/master/website/src/app/contacts
Maybe someone could make a StackBlitz as suggested!💪🤝👊
Final solution
In order to close this issue, I have decided to test in a simple case, as so the core idea under test would be the only concern. See the full simple application here before testing. And see the final testing file here.
I draw insights from both answers, and accepted the one that helped me the most, even the comments helped.
Thanks to everyone!
Upvotes: 4
Views: 7158
Reputation: 4993
I like this approach of testing bindings and do the same in my code as well. The provided test is almost right. Just a small issue with asynchronicity. The code inside fixture.whenStable().then(
would be executed after the test would be considered by karma as finished. You can avoid it by leveraging the async
/await
syntax.
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
}));
Upvotes: 1
Reputation: 19288
I think you should test the component individually. In your ContactListComponent
, test if it contains the right contacts. Something like the simple test below. Testing the child is a completly other test so mock the child component.
it('have the correct contacts', () => {
expect(component.contacts[0].id).toBe(1);
expect(component.contacts[0].name).toBe('value');
});
Simple mock example:
@Component({
selector: 'child-component'
template: ''
})
class TestChildComponent {
@Input() contact;
}
Now, you want to test your ChildComponent
. This can be done by stubbing a parent and test if the child reflects the correct data.
Example:
@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')
});
});
EDIT:
Instead of mocking the child component, you could instead add the CUSTOM_ELEMENTS_SCHEMA
.
TestBed.configureTestingModule({
schemas: [ CUSTOM_ELEMENTS_SCHEMA ],
...
});
This will prevent angular from raising errors when components could not be found. Use with caution because this could give the false impression everything is fine.
Upvotes: 1