threxx
threxx

Reputation: 1243

Angular2 - Test a function that calls a http service inside

I am currently trying to test a function which calls a http request service and then does something in the subscribe part (calls a function and sets a variable). My approach so far was to just call the function and I thought the request-service will be called automatically and so the subscribe part will be executed. However, I feel like this is not the way of doing it as it's not working.

The function I want to test:

  public trainBot() {
    this.isTraining = true;
    this.requestsService.trainModel(this.botId, false)
      .subscribe(response => {
        this.trainingStatus = this.trainingStatusMapping[response['status']];
        this.pollTrainingStatus();
      });
  }

My test so far (which does not work).

it('should poll the training status', () => {
    spyOn(component, 'pollTrainingStatus').and.callThrough();
    component.trainBot();
    fixture.detectChanges();
    expect(component.pollTrainingStatus).toHaveBeenCalled();
  });

So, can anyone tell me how to test that part inside the .subscribe(... part?

Update:

as someone suggested I added returnValue and async to my test. They are still not working, but looking like that now:

it('should poll the training status', fakeAsync(() => {
    component.trainBot();
    spyOn(service, 'trainModel').and.returnValue(Observable.of({'status': 'training'}));
    spyOn(component, 'pollTrainingStatus').and.callThrough();
    fixture.detectChanges();
    tick(1);
    expect(service.trainModel).toHaveBeenCalled();
    expect(component.pollTrainingStatus).toHaveBeenCalled();
  }));

The Error is the same

Upvotes: 3

Views: 8534

Answers (2)

Jason Spradlin
Jason Spradlin

Reputation: 1427

Firstly, You need to create your Spies BEFORE you run the method trainBot(). That should fix your tests.

it('should poll the training status', () => {
  spyOn(service, 'trainModel').and.returnValue(Observable.of({'status': 'training'}));
  spyOn(component, 'pollTrainingStatus');

  component.trainBot();

  expect(service.trainModel).toHaveBeenCalled();
  expect(component.pollTrainingStatus).toHaveBeenCalled();
}));

However, let's talk about your testing strategies.

To be honest, you're testing a component, not the service, so you shouldn't be letting the service method call through.

spyOn(service, 'trainModel').and.returnValue(Observable.of({'status': 'training'}));

Additionally, a unit test should test the smallest thing possible. You're actually testing the callback here, and the callback should probably be a named method instead of an anonymous arrow function. Then you can test and verify the callback's functionality in other tests.

public trainBot() {
  this.isTraining = true;
  this.requestsService.trainModel(this.botId, false)
      .subscribe(response => this.onTrainbotSuccess(response));
}

public onTrainbotSuccess(response) {
  this.trainingStatus = this.trainingStatusMapping[response['status']];
  this.pollTrainingStatus();
}

In this test, you can test that the response method is getting called

it('should call service.trainModel', () => {
  spyOn(service, 'trainModel').and.returnValue(Observable.of({'status': 'training'}));

  component.trainBot();

  expect(service.trainModel).toHaveBeenCalled();
});

it('should send success responses to onTrainbotSuccess()', () => {
  spyOn(component, 'onTrainbotSuccess');
  spyOn(service, 'trainModel').and.returnValue(Observable.of({'status': 'training'}));

  component.trainBot();

  expect(component.onTrainbotSuccess).toHaveBeenCalled();
});

Now we can write tests for specifically what the success callback does.

it('should poll the training status', () => {
  spyOn(component, 'pollTrainingStatus');

  component.onTrainbotSuccess({'status': 'training'});

  expect(component.pollTrainingStatus).toHaveBeenCalled();
});

Upvotes: 9

buoto
buoto

Reputation: 415

The function passed to subscribe() is called asynchronously. This means that your assertion is run before this.pollTrainingStatus() is called. You need to use async() or fakeAsync() utility function.

In code can look like this (if using fakeAsync()):

import { fakeAsync } from '@angular/core/testing';

it('should poll the training status', fakeAsync(() => {
  ...
  expect(component.pollTrainingStatus).toHaveBeenCalled();
}));

Upvotes: 0

Related Questions