The function I developed allows for an optional array of objects containing a name and value property. My goal is to have the value property automatically infer or pass the type of its value. I have successfully implemented this with a single object, but when there are multiple objects, it retains the type of the first object only. Below is the utility function:
import { Type } from '@angular/core'
import { ComponentFixture, TestBed } from '@angular/core/testing'
export type InputSignal = Record<string, any>
export type AdditionalProvider<K, V> = { name: K; value: Type<V> }
export type SetupTestOptions<K extends string, V> = {
setInput?: InputSignal
additionalProviders?: AdditionalProvider<K, V>[]
}
export function setupTest<T extends object, K extends string, V>(
Component: Type<T>,
options: SetupTestOptions<K, V> = {}
) {
const fixture: ComponentFixture<T> = TestBed.createComponent(Component)
const component: T = fixture.componentInstance
if (options.setInput) {
Object.keys(options.setInput).forEach(key => {
if (!options.setInput) return
fixture.componentRef.setInput(key, options.setInput[key])
})
}
const providers = <Record<K, V>>{}
if (options.additionalProviders) {
options.additionalProviders.forEach(({ name, value }) => {
providers[name] = TestBed.inject(value) <-- here is where I would like the types to be inferred.
})
}
fixture.detectChanges()
return { fixture, component, ...providers }
}
Below is an example showcasing how the function is used:
it('should route to the dashboard/home route if projectId is null', async () => {
const { fixture, component, location, ngZone, router } = setupTest(DashboardComponent, {
additionalProviders: [
{ name: 'location', value: Location },
{ name: 'router', value: Router },
{ name: 'ngZone', value: NgZone }
]
})
ngZone.run(() => {
router.initialNavigation()
component.ngOnInit()
})
fixture.whenStable().then(() => {
expect(location.path()).toBe('/dashboard/home')
})
})
I have experimented with different approaches, such as having type V
extend various Angular utility types and explicitly defining the types in this specific scenario. The closest I have come is creating a union of the three different services (Location | Router | NgZone
), but this requires explicit casting when using them. My desire is for TypeScript to automatically determine the correct type based on the value and assign that type to the destructured name in the example.