'How to test RxJs observable callback with NgRx setState

I would like to know how to test the code inside a .subscribe callback, with the subscription being on a NgRx store selector.

Environment: Angular 13, RxJs 7+, NgRx 13, Jest 27

Consider

my-component.ts

...
ngOnInit {
  this.myValue = true;
  this.store.select(mySelector).pipe(filter(data => data.attribute === true)).subscribe(data => {
    this.myValue = false; // I want to test this
  }
}
...

my-component.spec.ts

describe('MyComponent', () => {
  let component: MyComponent;
  let fixture: ComponentFixture<MyComponent>;
  let store: MockStore;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [MyComponent],
      imports: [...],
      providers: [
        provideMockStore({
          initialState: { myFeature: { } },
        }),
      ],
    }).compileComponents();

    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();

    store = TestBed.inject(MockStore);
  });

  it('should perform animation & redirect to /dashboard if login successful', () => {
    store.setState({
      myFeature: {
        ...
        attribute: true,
      },
    });

    expect(component.myValue).toBe(false);
  });

This works, but it's random. Since this is asynchronous I could test myValue before the subscribe callback has been called and the test would fail, for example if my subscribe callback takes time to do stuff, like so (I'm adding a delay of 500ms) :

...
ngOnInit {
  this.myValue = true;
  this.store.select(mySelector).pipe(filter(data => data.attribute === true), delay(500)).subscribe(data => {
    this.myValue = false; // I want to test this
  }
}
...

This fails.

How can I wait the callback to perform before testing my value ? I could wait an arbitrary time like 1 sec before testing, but it could break at any time in the future it's not robust enough. Like :

it('should perform animation & redirect to /dashboard if login successful', 
async () => {
    store.setState({
      myFeature: {
        ...
        attribute: true,
      },
    });
    await lastValueFrom(timer(600)); //rxjs 7
    expect(component.myValue).toBe(false);
  });

Thanks for your help



Solution 1:[1]

Use waitForAsync and fixture.whenStable to ensure completion of async tasks before evaluating the expect.

it('should perform animation & redirect to /dashboard if login successful', waitForAsync(() => {
    store.setState({
      myFeature: {
        ...
        attribute: true,
      },
    });
    
    fixture.whenStable()
      .then(() => expect(component.myValue).toBe(false));
  }));

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1