简体   繁体   中英

Unit testing service calls from an Angular component rxjs subscription

I have this Angular (v13) code in a component:

export class MyComp {
  destroy$ = new Subject<void>();
  submits$ = new Subject<MyFormState>(); // .next() in the template

  ngOnInit(){
    this.submits$.pipe(
      takeUntil(this.destroy$)
    ).subscribe(formState => this.service.doSomething(formState));
  }
}

and I am trying to unit test it using jasmine spy object:

describe('MyComp', () => {
  let submits$ = new Subject<MyFormState>();
  let mock = jasmine.createSpyObj('MyService', [ 'doSomething' ]);

  beforeEach(() => {
    TestBed.configureTestingModule({
      declarations: [ MyComp ],
      providers: [
        { provide: MyService, useValue: mock },
      ],
    });
  });

  it('should be called when clicking the button', () => {
    const f: MyFormState = { name: 'my name' };
    component.submits$.next(formState);

    waitForAsync(() => {
      expect(mock.doSomething).toHaveBeenCalledWith(f); // <-- false positives, anything passes
    });
  });
});

I have also tried another async pattern

it('should be called when clicking the button', fakeAsync(() => {
  // ...nexting

  tick();

  // without waitForAsync
  expect(mock.doSomething).toHaveBeenCalledWith(f);
}));

but in this case the method never gets called.

What am I doing wrong? What would be the correct way of testing this code without using done() ? Is it possible to use TestScheduler with this sort of anonymous subscription?

In your case you need to call fixture.detectChanges to get this to work.

it('should be called when clicking the button', () => {
  fixture.detectChanges(); // the first call starts the lifecycle hook

  const formState: MyFormState = { name: 'my name' };
  component.submits$.next(formState);

  expect(mock.doSomething).toHaveBeenCalledWith(formState); 

});

Now to tell you why this makes it work. The component as you are testing it includes the dom. Since you set up your subscription in the ngOnInit, the ngOnInit needs to execute to set that up.

With TestBed, ngOnInit isn't called until the first fixture.detectChanges() is called.

I know you haven't asked for this next part - my suggestion is to test your component by interacting with the html. That will provide you much better unit tests. This book has been the closest to what my approach is.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM