Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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?

like image 390
Juan Herrera Avatar asked May 27 '20 14:05

Juan Herrera


2 Answers

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();
  });
});
like image 127
satanTime Avatar answered Sep 24 '22 15:09

satanTime


In case you just need to get rid of the errors, you can import all the required modules like this:

   beforeEach(() => {
     TestBed.configureTestingModule({
       imports: [HttpClientTestingModule, EffectsModule.forRoot([]), StoreModule.forRoot({}), EntityDataModule.forRoot(entityConfig)],
     });
     service = TestBed.inject(PatientService);
   });
like image 32
Ahad Dastbelaraki Avatar answered Sep 23 '22 15:09

Ahad Dastbelaraki