Search code examples
angularjestjsintegration-testingngrxjasmine-marbles

Angular NgRx integration test not working (with Nx and Jest)


I have a facade class that is using NgRx behind the curtains. The facade itself is working when I use it in my application, but via the integration tests it doesn't work (better said: the tests are not working). I am using Nx (don't think that matters) and Jest.

Note: with integration test I mean that I don't want to mock the NgRx parts, I want to test the NgRx logic too. Only the dataservice is mocked.

The facade class is very simple. Here is an extract of it:

@Injectable({
  providedIn: 'root'
})
export class ProfileFacade {
  public isProfileLoaded$ = this.store.select(Selectors.isProfileLoaded);

  public constructor(private store: Store<fromProfile.PartialState>) {
    /* */
  }

  public loadProfile(): void {
    this.dispatch(Actions.loadProfile());
  }

  private dispatch(action: Action): void {
    this.store.dispatch(action);
  }
}

The dipatched loadProfile action triggers an effect that returns a ProfileLoadSucceeded action containing the data fetched via the ProfileService. The reducer then catches this action, puts the profile data in the store and also sets the isProfileLoaded property to true.

This is the setup of my integration test:

describe('ProfileFacade integration', () => {
  let facade: ProfileFacade;
  let store: Store<fromProfile.PartialState>;
  let profileServiceSpy: SpyObject<ProfileService>;

  beforeEach(() => {
    @NgModule({
      imports: [
        StoreModule.forFeature(fromProfile.PROFILE_FEATURE_KEY, fromProfile.reducer),
        EffectsModule.forFeature([ProfileEffects])
      ],
      providers: [
        ProfileFacade,
        mockProvider(ProfileService),
      ]
    })
    class CustomFeatureModule {}

    @NgModule({
      imports: [NxModule.forRoot(), StoreModule.forRoot({}), EffectsModule.forRoot([]), CustomFeatureModule]
    })
    class RootModule {}
    TestBed.configureTestingModule({ imports: [RootModule] });

    store = TestBed.inject(Store);
    facade = TestBed.inject(ProfileFacade);
    profileServiceSpy = TestBed.inject(ProfileService) as SpyObject<ProfileService>;
  });

  ...
});

Here is a selection of many things I've tried in my integration tests:

  1. With expect(...).toBeObservable(...)

      it(`should set isProfileLoaded to true`, () => {
        profileServiceSpy.get.and.returnValue(hot('^x|', { x: profile }));  // I've tried several variations of hot and cold
        facade.loadProfile();
    
        const expected = cold('--b', { b: true });
        expect(facade.isProfileLoaded$).toBeObservable(expected);
      });
    
  2. With toPromise() and then()

      it(`should set isProfileLoaded to true`, () => {
        // same 2 lines as in 1)
    
        return facade.isProfileLoaded$.toPromise().then(result => expect(result).toBe(true));
      });
    
  3. With expect(...toPromise()).resolves()

      it(`should set isProfileLoaded to true`, () => {
        // same 2 lines as in 1)
    
        return expect(facade.isProfileLoaded$.toPromise()).resolves.toBe(true);
      });
    
  4. With subscribe and done()

      it(`should set isProfileLoaded to true`, done => {
        // same 2 lines as in 1)
    
        facade.isProfileLoaded$.subscribe(result => {
          expect(result).toBe(true);
          done();
        });
      });
    

I'm just showing the tests for isProfileLoaded, but I have the same problem with the tests of the data itself. The resulting value is always false or null: the facade's selector result doesn't seem to reflect the store's content.

The store itself seems to work fine though. By adding console.log statements at different places I can see that the data from the mocked service is flowing as it should. It's there in the effect, the reducer and the resulting state.

I guess I am missing something obvious but what? I haven't found examples that are performing this kind of integration tests, with this combination of technologies.


Solution

  • As you are using Nx already, one way I can think of is this:

    import { readFirst } from '@nrwl/nx/testing';
    
    it(`should set isProfileLoaded to true`, async (done) => {
      try {
        profileServiceSpy.get.and.returnValue(of(profile));
    
        facade.loadProfile();
    
        const isProfileLoaded = await readFirst(facade.isProfileLoaded$);
    
        expect(isProfileLoaded).toBeTruthy();
    
        done();
      } catch (error) {
        done.fail(error);
      }
    });