angularunit-testingngrxngrx-data

How to unit test a EntityCollectionServiceBase in @ngrx/data?


I have a dummy service:

export class PatientService extends EntityCollectionServiceBase<Patient> {
  constructor(serviceElementsFactory: EntityCollectionServiceElementsFactory) {
    super('Patient', serviceElementsFactory);
  }
}

I have the following test:

describe('PatientService', () => {
  let service: PatientService;

  beforeEach(() => {
    TestBed.configureTestingModule({});
    service = TestBed.inject(PatientService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

And it gives me:

NullInjectorError: No provider for EntityCollectionServiceElementsFactory

So I updated as follows:

describe('PatientService', () => {
  let service: PatientService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [EntityDataModuleWithoutEffects.forRoot(entityConfig)],
      providers: [provideMockStore()],
    });
    service = TestBed.inject(PatientService);
  });

  it('should be created', () => {
    expect(service).toBeTruthy();
  });
});

But now it gives me:

NullInjectorError: No provider for ScannedActionsSubject!

How can I properly fix my test?


Solution

  • Updated 2022-08-06

    Because of changes in the latest version of ngrx, you need to import an empty store modules and HttpClientTestingModule.

    Please check the example below:

    describe('PatientService', () => {
      let service: PatientService;
    
      beforeEach(() => {
    
        TestBed.configureTestingModule({
          providers: [
            PatientService, // <- your entity collection service
    
            // a way to mock the store selectors
            provideMockStore(),
          ],
          imports: [
            EntityDataModule.forRoot({
              entityMetadata: {
                Patient: {}, // <- change to your entity
              },
              pluralNames: {
                Patient: 'Patients',  // <- change to your entity
              },
            }),
    
            // can be kept as it is if there are no more dependencies
            StoreModule.forRoot({}),
            EffectsModule.forRoot([]),
            HttpClientTestingModule,
          ],
        });
        service = TestBed.inject(PatientService);
      });
    
      it('should be created', () => {
        expect(service).toBeTruthy();
      });
    });
    

    Original answer

    Yep, you are very close, do like that. You need to provide in testbed everything what is required for the configuration.

    In our case it means that PatientService should be provided as the testing unit and EntityCollectionServiceElementsFactory should be provided as its dependency.

    But EntityCollectionServiceElementsFactory has own dependencies: EntityDispatcherFactory, EntityDefinitionService, EntitySelectorsFactory and EntitySelectors$Factory. Which we don't want to provide because they might have own dependencies making our live worse.

    To fix it let's simply sub EntityCollectionServiceElementsFactory.

    describe('PatientService', () => {
      let service: PatientService;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          providers: [
            PatientService,
            {
              provide: EntityCollectionServiceElementsFactory,
              // our stub
              useValue: {
                methodsToFake: jasmine.createSpy(),
                // ....
              },
            },
          ],
        });
        service = TestBed.inject(PatientService);
      });
    
      it('should be created', () => {
        expect(service).toBeTruthy();
      });
    });
    

    Also to avoid the pain of mocking dependencies and its methods in future you can use ng-mocks. Then the test could looks like that:

    describe('PatientService', () => {
      let service: PatientService;
    
      beforeEach(() => {
        TestBed.configureTestingModule({
          providers: [
            PatientService,
            {
              provide: EntityCollectionServiceElementsFactory,
              useValue: MockService(EntityCollectionServiceElementsFactory),
            },
          ],
        });
        service = TestBed.inject(PatientService);
      });
    
      it('should be created', () => {
        expect(service).toBeTruthy();
      });
    });