I've recently upgraded to my project to use Angular 15.2.4, Jest 29.5, and NgRx 15.4.
My unit tests dealing with components containing NgRx Entity services have broken.
This is the entity Service:
import { Injectable } from '@angular/core';
import { EntityCollectionServiceBase, EntityCollectionServiceElementsFactory } from '@ngrx/data';
import { Site } from 'app/models/site.model';
@Injectable({ providedIn: 'root' })
export class SiteEntityService extends EntityCollectionServiceBase<Site> {
constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
super('Site', serviceElementsFactory);
}
}
This is the component
import { Component, OnDestroy, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Observable } from 'rxjs';
import { Site } from 'app/models/site.model';
import { SiteEntityService } from 'app/store/site-entity.service';
import { Breadcrumb } from 'app/theme/shared/components/breadcrumb/models/breadcrumb.model';
import { BreadcrumbService } from 'app/theme/shared/components/breadcrumb/services/breadcrumb.service';
@Component({
selector: 'app-site-search-shell',
templateUrl: './site-search-shell.component.html',
styleUrls: ['./site-search-shell.component.scss'],
})
export class SiteSearchShellComponent implements OnInit {
sites$: Observable<Site[]>;
constructor(
private siteEntityService: SiteEntityService,
private breadCrumbService: BreadcrumbService,
private router: Router,
) {
this.sites$ = siteEntityService.entities$;
this.breadCrumbService.add(new Breadcrumb('Sites', '/sites'));
}
ngOnInit(): void {
this.siteEntityService.getWithQuery('$sort_by=name');
}
addClicked(): void {
this.router.navigateByUrl('/sites/create');
}
}
This is the non-working test
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { MockComponent } from 'ng-mocks';
import { SiteSearchDisplayComponent } from '../site-search-display/site-search-display.component';
import { RouterTestingModule } from '@angular/router/testing';
import { Router } from '@angular/router';
import { BreadcrumbService } from 'app/theme/shared/components/breadcrumb/services/breadcrumb.service';
import { SiteSearchShellComponent } from './site-search-shell.component';
import { of } from 'rxjs';
import { EntityCollection, EntityCollectionServiceBase, EntityCollectionServiceElements, EntityCollectionServiceElementsFactory, EntityDefinitionService, EntityDispatcherFactory, EntitySelectors$Factory, EntitySelectorsFactory } from '@ngrx/data';
import { Site } from 'app/models/site.model';
import { provideMockStore } from '@ngrx/store/testing';
import { SiteEntityService } from 'app/store/site-entity.service';
describe('SiteSearchShellComponent', () => {
let component: SiteSearchShellComponent;
let fixture: ComponentFixture<SiteSearchShellComponent>;
let service: SiteEntityService;
let router: Router;
beforeEach(
async () => {
TestBed.configureTestingModule({
declarations: [
SiteSearchShellComponent,
MockComponent(SiteSearchDisplayComponent)
],
providers: [
SiteEntityService,
{
provide: EntityCollectionServiceElementsFactory,
useValue: {
methodsToFake: jest.fn().mockImplementation(() => {}),
}
},
BreadcrumbService
],
imports: [RouterTestingModule],
teardown: { destroyAfterEach: false }
}).compileComponents();
service = TestBed.inject(SiteEntityService);
router = TestBed.inject(Router);
});
beforeEach(() => {
fixture = TestBed.createComponent(SiteSearchShellComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
test('should create', () => {
expect(component).toBeTruthy();
});
test('should route to sites/create when addCreate event called', () => {
jest.spyOn(component, 'addClicked');
const navigateSpy = spyOn(router, 'navigateByUrl');
component.addClicked();
fixture.detectChanges();
expect(component.addClicked).toBeCalledTimes(1);
expect(navigateSpy).toBeCalledTimes(1);
expect(navigateSpy).toBeCalledWith('/sites/create');
});
});
I get the error:
● SiteSearchShellComponent › should create
TypeError: serviceElementsFactory.create is not a function
6 | export class SiteEntityService extends EntityCollectionServiceBase<Site> {
7 | constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
> 8 | super('Site', serviceElementsFactory);
| ^
9 | }
10 | }
11 |
at new EntityCollectionServiceBase (node_modules/@ngrx/data/fesm2020/ngrx-data.mjs:2221:78)
at new SiteEntityService (src/app/store/site-entity.service.ts:8:5)
at Object.SiteEntityService_Factory [as factory] (ng:\SiteEntityService\ɵfac.js:5:10)
at R3Injector.hydrate (node_modules/@angular/core/fesm2020/core.mjs:8031:35)
at R3Injector.get (node_modules/@angular/core/fesm2020/core.mjs:7919:33)
at R3Injector.<anonymous> (node_modules/ng-mocks/webpack:/ng-mocks/libs/ng-mocks/src/lib/common/ng-mocks-global-overrides.ts:371:53)
at R3Injector.this (node_modules/ng-mocks/webpack:/ng-mocks/libs/ng-mocks/src/lib/mock-service/helper.create-clone.ts:10:65)
at TestBedImpl.inject (node_modules/@angular/core/fesm2020/testing.mjs:24201:52)
at Function.inject (node_modules/@angular/core/fesm2020/testing.mjs:24060:37)
at src/app/areas/sites/site-search/site-search-shell/site-search-shell.component.spec.ts:41:25
at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:416:30)
at ProxyZoneSpec.Object.<anonymous>.ProxyZoneSpec.onInvoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:3728:43)
at _ZoneDelegate.Object.<anonymous>._ZoneDelegate.invoke (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:415:56)
at Zone.Object.<anonymous>.Zone.run (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:173:47)
at Object.wrappedFunc (node_modules/zone.js/bundles/zone-testing-bundle.umd.js:4208:34)
I have changed the test from the previously working version to try to get it running with the new versions.
After a bit of work, I managed to get it working:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable, of } from 'rxjs';
import { Site } from 'app/models/site.model';
import { SiteEntityService } from 'app/store/site-entity.service';
import { Breadcrumb } from 'app/theme/shared/components/breadcrumb/models/breadcrumb.model';
import { BreadcrumbService } from 'app/theme/shared/components/breadcrumb/services/breadcrumb.service';
import { SiteSearchShellComponent } from './site-search-shell.component';
import { createSpyObj } from 'jest-createspyobj';
import { Router } from '@angular/router';
import { MockComponent } from 'ng-mocks';
import { SiteSearchDisplayComponent } from '../site-search-display/site-search-display.component';
describe('SiteSearchShellComponent', () => {
let component: SiteSearchShellComponent;
let fixture: ComponentFixture<SiteSearchShellComponent>;
let siteEntityServiceSpy: jest.Mocked<SiteEntityService>;
let breadCrumbServiceSpy: jest.Mocked<BreadcrumbService>;
let routerSpy = {
navigateByUrl: jest.fn(),
} as Partial<Router> as jest.Mocked<Router>;
beforeEach(async () => {
siteEntityServiceSpy = {
entities$: of([]),
getWithQuery: jest.fn(),
} as Partial<SiteEntityService> as jest.Mocked<SiteEntityService>;
breadCrumbServiceSpy = {
add: jest.fn(),
} as Partial<BreadcrumbService> as jest.Mocked<BreadcrumbService>;
routerSpy = {
navigateByUrl: jest.fn(),
} as Partial<Router> as jest.Mocked<Router>;
await TestBed.configureTestingModule({
declarations: [SiteSearchShellComponent, MockComponent(SiteSearchDisplayComponent)],
imports: [RouterTestingModule],
providers: [
{ provide: SiteEntityService, useValue: siteEntityServiceSpy },
{ provide: BreadcrumbService, useValue: breadCrumbServiceSpy },
{ provide: Router, useValue: routerSpy },
],
}).compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(SiteSearchShellComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create the component', () => {
expect(component).toBeTruthy();
});
});