spectator
spectator copied to clipboard
not really clear that inputs can ONLY be assigned using props :/
Is this a regression?
No
Description
I tried to use Spectator as a drop-in replacement for a component unit-test and after re-writing the TestBed
to Spectator
s tinier boilerplate 🎊 I could not get past this error:
TypeError: Cannot read properties of undefined (reading 'id')
at templateFn (ng:///CompiledComponent.js:147:54)
at executeTemplate (node_modules/@angular/core/fesm2015/core.mjs:9632:9)
at refreshView (node_modules/@angular/core/fesm2015/core.mjs:9495:13)
at refreshComponent (node_modules/@angular/core/fesm2015/core.mjs:10692:13)
at refreshChildComponents (node_modules/@angular/core/fesm2015/core.mjs:9291:9)
at refreshView (node_modules/@angular/core/fesm2015/core.mjs:9545:13)
at renderComponentOrTemplate (node_modules/@angular/core/fesm2015/core.mjs:9612:9)
at tickRootContext (node_modules/@angular/core/fesm2015/core.mjs:10866:9)
at detectChangesInRootView (node_modules/@angular/core/fesm2015/core.mjs:10891:5)
at RootViewRef.detectChanges (node_modules/@angular/core/fesm2015/core.mjs:21505:9)
TypeError: Cannot read properties of undefined (reading 'detectChanges')
at UserContext.apply (path.component.spec.ts:68:13)
at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:409:30)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:303:43)
at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:408:56)
at Zone.run (node_modules/zone.js/dist/zone.js:169:47)
at runInTestZone (node_modules/zone.js/dist/zone-testing.js:583:38)
at UserContext.<anonymous> (node_modules/zone.js/dist/zone-testing.js:598:24)
at <Jasmine>
that I got because I kept the original @Input assignment, in my beforeEach()
after spectator = createComponent()
:
component.task = { ...testTask };
This does not work, you need to use the props
property of createComponent()
to assign @Inputs.
I think this needs to be clarified better in the docs.
The error given is not descriptive: It emanates from compiled component javascript, and not the spec file, so my immediate reaction was to change the only line of code in the component containing the word id
:
this.users$ = this.someService.someMethod(this.task.id) // changed to: this.task.thisIDbroken
to see if this is what was causing the error, but it was not.
Please provide a link to a minimal reproduction of the bug
N/A
Please provide the exception or error you saw
TypeError: Cannot read properties of undefined (reading 'id')
at templateFn (ng:///CompiledComponent.js:147:54)
at executeTemplate (node_modules/@angular/core/fesm2015/core.mjs:9632:9)
at refreshView (node_modules/@angular/core/fesm2015/core.mjs:9495:13)
at refreshComponent (node_modules/@angular/core/fesm2015/core.mjs:10692:13)
at refreshChildComponents (node_modules/@angular/core/fesm2015/core.mjs:9291:9)
at refreshView (node_modules/@angular/core/fesm2015/core.mjs:9545:13)
at renderComponentOrTemplate (node_modules/@angular/core/fesm2015/core.mjs:9612:9)
at tickRootContext (node_modules/@angular/core/fesm2015/core.mjs:10866:9)
at detectChangesInRootView (node_modules/@angular/core/fesm2015/core.mjs:10891:5)
at RootViewRef.detectChanges (node_modules/@angular/core/fesm2015/core.mjs:21505:9)
TypeError: Cannot read properties of undefined (reading 'detectChanges')
at UserContext.apply (path.component.spec.ts:68:13)
at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:409:30)
at ProxyZoneSpec.onInvoke (node_modules/zone.js/dist/zone-testing.js:303:43)
at _ZoneDelegate.invoke (node_modules/zone.js/dist/zone.js:408:56)
at Zone.run (node_modules/zone.js/dist/zone.js:169:47)
at runInTestZone (node_modules/zone.js/dist/zone-testing.js:583:38)
at UserContext.<anonymous> (node_modules/zone.js/dist/zone-testing.js:598:24)
at <Jasmine>
Please provide the environment you discovered this bug in
Angular / Karma / Jasmine locally
Anything else?
fixed by using props:
props: {
task: { ...testTask },
},
Do you want to create a pull request?
Yes
You can also assign an input with spectator.setInput('task', myTask)
or spectator.component.task = myTask
Your createComponent
function is initializing your component, which seems to call component.task.id
, maybe in ngOnInit()
(not enough code to guess more)
spectator = createComponent();
spectator.setInput('task', myTask);
If you do as above, you're initializing the component, and having the issue because component.task is undefined during this step. The task would be set only after that, once it's too late
An other way is to play with the detectChanges property, but it has other impacts
spectator = createComponent({detectChanges: false});
spectator.component.task= myTask;
// Do all kind of initialization
spectator.detectChanges();
So, your issue is specific to your usecase. Using props: { task: myTask }
is the best choice here, because you need it on init, but there are other ways to set inputs after the test init
In addition to the above, you can also wrap your component with a HostComponent using the createHostFactory
, and pass Input props when the component is instantiated also. This is useful if you need to test more complex Input behavior (although I would default to props
or setInput
first).