angularjestjsng-mocksjest-preset-angularangular-query-signals

angular jest unit test fails on contentChildren, but works with @ContentChildren decorator


I'm trying to write jest unit tests for a basic angular component. But I seem to have stumbled on a strange error. A minimal repro is hosted here.

When running the test using VS Code and the Jest Runner extension, I get the following error

TypeError: Cannot read properties of undefined (reading 'Symbol(SIGNAL)')

Here's my specfile

@Component({
    selector: 'demo-checkbox-test',
    template: `
        <div bsCheckboxGroup>
            <bs-checkbox>
                This is a checkbox
            </bs-checkbox>
        </div>`
})
class BsCheckboxTestComponent { }

describe('BsCheckboxComponent', () => {
    let component: BsCheckboxTestComponent;
    let fixture: ComponentFixture<BsCheckboxTestComponent>;

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            imports: [
                // Mock dependencies
            ],
            declarations: [
                // Unit to test
                BsCheckboxComponent,

                // Mock dependencies
                MockDirective(BsCheckboxGroupDirective),

                // Testbench
                BsCheckboxTestComponent,
            ]
        }).compileComponents();

        fixture = TestBed.createComponent(BsCheckboxTestComponent);
        await fixture.whenStable();
        component = fixture.componentInstance;
        fixture.detectChanges();
    });

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

And here's my unit-to-test

@Component({
    selector: 'bs-checkbox',
    template: `
        <label>
            <input type="checkbox">
        </label>`
})
export class BsCheckboxComponent {}

The testbed mocks the following directive:

@Directive({
    selector: '[bsCheckboxGroup]',
})
export class BsCheckboxGroupDirective {
    name = input.required<string>();
    children = contentChildren(forwardRef(() => BsCheckboxComponent));
    // @ContentChildren(forwardRef(() => BsCheckboxComponent)) children!: QueryList<BsCheckboxComponent>;
}

Remarkably when using the @ContentChildren(...) decorator instead of the contentChildren(...) function, the test runs perfectly fine.

What's causing this? How can I fix it?

EDIT: It seems there is an existing issue about this


Solution

  • Here's what you can do while we wait for the open PR to be merged.

    Make sure the Component you are trying to test is standalone, in your example that'd be the BsCheckboxTestComponent

    Split your TestBed setup in Configuration and Initialization:

    beforeEach(async () => {
        await TestBed.configureTestingModule({
            imports: [
                // Mock dependencies
            ],
            declarations: [
                // Testbench
                BsCheckboxTestComponent,
                // Don't Mock your Directive here!
                // MockDirective(BsCheckboxGroupDirective)
            ]
        }).compileComponents();
    });
    
    // Do it here by replacing it from your Component's imports
    beforeEach(() => {
        TestBed.overrideComponent(BsCheckboxTestComponent, {
            remove: { imports: [BsCheckboxGroupDirective] },
            add: { imports: [BsCheckboxGroupMockDirective] }
        });
    });
    
    // Now we can initialize it
    beforeEach(async () => {
        fixture = TestBed.createComponent(BsCheckboxTestComponent);
        await fixture.whenStable();
        component = fixture.componentInstance;
        fixture.detectChanges();
    });
    

    The last piece is a manually created Mock of your BsCheckGroupDirective:

    @Directive({
        selector: '[bsCheckboxGroup]',
        standalone: true
    })
    class BsCheckGroupMockDirective{
        public name= input<string>();
    }
    

    Hope it helps!