Let's say I have a simple component with signal input introduced in Angular 17.1
@Component({
selector: 'greet',
template: `
<span class="greet-text">{{firstName()}}</span>`,
standalone: true,
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class GreetComponent {
firstName = input.required<string>();
}
How can I instantiate this component from TestBed
and assign signal inputs?
The only solution I found in Angular repo, it to wrap it into another component without signal input.
Is it the only way to test new inputs? Doubling number of components...
describe('greet component', () => {
it('should allow binding to an input', () => {
const fixture = TestBed.createComponent(TestCmp);
fixture.detectChanges();
});
});
@Component({
standalone: true,
template: `<greet [firstName]="firstName" />`,
imports: [GreetComponent],
})
class TestCmp {
firstName = 'Initial';
}
As stated in docs of InputFuction
interface, InputSignal
is non-writable:
/**
* `InputSignal` is represents a special `Signal` for a directive/component input.
*
* An input signal is similar to a non-writable signal except that it also
* carries additional type-information for transforms, and that Angular internally
* updates the signal whenever a new value is bound.
*
* @developerPreview
*/
However, Alex Rickabaugh pointed out in this comment:
When you create a component "dynamically" (e.g. with
ViewContainerRef.createComponent
) then you "control" the component and its inputs via itsComponentRef
.ComponentRef
has.setInput
to update the values for inputs of that component.In tests,
ComponentFixture
exposes the.componentRef
so you can use.setInput
to test the component by manipulating its inputs. All of this works today, and will work with signal components too.
This now works with jest-preset-angular v14.0.2 and Angular 17.3.2.
import { ComponentRef } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { GreetComponent } from './greet.component'
describe('GreetComponent', () => {
let component: GreetComponent
let componentRef: ComponentRef<GreetComponent>
let fixture: ComponentFixture<GreetComponent>
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [GreetComponent],
}).compileComponents()
fixture = TestBed.createComponent(GreetComponent)
component = fixture.componentInstance
componentRef = fixture.componentRef
componentRef.setInput('firstName', 'Feyd-Rautha')
fixture.detectChanges()
})
it('should create', () => {
expect(component).toBeTruthy()
})
})
As George Knap has pointed out, you can use componentRef.setInput(name: string, value: unknown)
.
To make this more type-safe, you could write it like this:
fixture.componentRef.setInput(
'key' satisfies keyof typeof component,
value satisfies ReturnType<(typeof component)['key']>,
);
If you want to avoid doing this over and over again, you could also write a function like this:
/**
* This updates an input signal of the {@link fixture.componentInstance} component in a type-safe way.
* @param fixture The fixture of the component.
* @param key The key of the input signal.
* @param value The new value passed to the input signal.
*/
function updateInputSignal<
ComponentType,
Key extends keyof ComponentType,
ValueType extends ComponentType[Key] extends InputSignal<infer U> ? U : never,
>(fixture: ComponentFixture<ComponentType>, key: Key & string, value: ValueType) {
fixture.componentRef.setInput(key, value);
}
// And use it:
updateInputSignal(fixture, 'key', value);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With