crgolden
crgolden

Reputation: 4634

How to test a function with a Subscription that returns void

I have a TypeScript ModelService with an edit function:

edit(model: Model): Observable<Model> {
  const body = JSON.stringify(model);
  return this.http.put(`/models/edit/${model.id}`, body));
}

And I have a TypeScript EditComponent with an edit function that subscribes to the service and navigates when it gets the response:

edit(model: Model): void {
  this.service
    .edit(model)
    .subscribe(() => this.router.navigate([`/models/details/${model.id}`]);
}

What is the best way to test this component's edit function?

I have a Jasmine test that does this:

// Setup
TestBed.configureTestingModule({
  declarations: [EditComponent],
  providers: [
    {
      provide: ModelService,
      useValue: jasmine.createSpyObj('ModelService', ['edit'])
    }
  ]
});
const fixture = TestBed.createComponent(EditComponent);
const component = fixture.componentInstance;
const modelService = fixture.debugElement.injector.get(ModelService);
fixture.detectChanges();

// Test
it('should call edit', () => {
  fakeAsync(() => {
    component.edit(model);
    expect(modelService.edit).toHaveBeenCalled();
  });
});

But with this test, I always get SPEC HAS NO EXPECTATIONS when it runs. My understanding of fakeAsync is that it runs synchronously so I thought this would work.

I've also tried using numerous variations of async, tick(), and done(), but those either give the same message or fail with Cannot read property 'subscribe' of undefined when calling the component's edit function.

In other tests, I've been able to use return fixture.whenStable().then() and that works fine (as described here), but I don't think it works here since the component function is returning void instead of a Promise.

What is a better way to test this component function?

Upvotes: 5

Views: 6406

Answers (1)

mixth
mixth

Reputation: 636

For SPEC HAS NO EXPECTATIONS

That doesn't seem like the right way to use fakeAsync and that is why it said no expectations. It should be:

it('should call edit', fakeAsync(() => {
  component.edit(model);
  expect(modelService.edit).toHaveBeenCalled();
});

Anyway, the modelService.edit() is actually a call that can be expected synchronously, because it is called inside component.edit().

So you can simply test it like this:

it('should call edit', () => {
  component.edit(model);
  expect(modelService.edit).toHaveBeenCalled();
});

For Cannot read property 'subscribe' of undefined

Because you create spy object without any returns. Hence, modalService.edit() return undefined and cannot be read property 'subscribe'. In this case, I normally stub its result by creating new Observable or using of() and return values that this component may need to interact with.

jasmine.createSpyObj('ModelService', { edit: of() })

Upvotes: 2

Related Questions