Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

unit testing spyOn observable service in angular2

I have a service (ChildService) which depends on another service (InteractWithServerService). The latter service (InteractWithServerService) is used to make server calls and return an observable of "any" type. For simplicity let's assume it returns an observable. I am trying to write a unit test for ChildService.

ChildService

@Injectable()
export class ApplicationService {
    constructor(private  interactWithServerService:InteractWithServerService){;}

    public GetMeData():string {
        var output:string;       
        this.interactWithServerService.get("api/getSomeData").
           subscribe(response =>{console.log("server response:", response);
           output=response});        
         return output;
    }
}

ServerInteractionService

@Injectable()
export class InteractWithServerService {        
    constructor(private http: Http) {
        ;
    }    
    get(url: string): Observable<any> {        
        return this.http.get(this.url);
    }       
}

The test case works fine when I mock the dependent service. i.e.,

class MockInteractWithServerService {
    get() {
        return Observable.of("some text");
    }           
}

describe('Service:ChildService', () => {
    let childService: ChildService;

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
             { provide: InteractWithServerService, useClass: MockInteractWithServerService },
                ChildService],
        });


    beforeEach(inject([ChildService], (actualService: ChildService) => {
        childService= actualService;        
    }));

    fit('should call server-call testCall()', () => {
        let actualReturnvalue= childService.GetMeData();        
        expect(actualReturnvalue).toBe("some text");
    });
});

The above method is not preferred as I might end up writing "n" mock classes for "n" dependencies. So I want to create my unit tests using spyOn. However, the test case doesn't work and throws "Error: No provider for Http!". While I understand what the error is, I would like to know why it is thrown although I am spying on the dependent service. Looks like the "spyOn" is not working.

describe('Service:ChildService', () => {
    let childService: ChildService;

    beforeEach(() => {
        TestBed.configureTestingModule({
            providers: [
             InteractWithServerService,
                ChildService],
        });

        spyOn(InteractWithServerService.prototype, 'get').and
             .callFake(()=>      
          {return Observable.of("some text");});       
    });
    beforeEach(inject([ChildService], (actualService: ChildService) => {
        childService= actualService;        
    }));

    fit('should call server-call testCall()', () => {
        let actualReturnvalue= childService.GetMeData();        
        expect(actualReturnvalue).toBe("some text");
    });
});

Am I missing something obvious?

like image 884
Sudhir V Avatar asked Feb 21 '17 04:02

Sudhir V


People also ask

What is SpyOn in Angular unit testing?

SpyOn is a Jasmine feature that allows dynamically intercepting the calls to a function and change its result.

How do you write a test case for services in Angular 10?

To test a service, you set the providers metadata property with an array of the services that you'll test or mock. content_copy let service: ValueService; beforeEach(() => { TestBed. configureTestingModule({ providers: [ValueService] }); }); Then inject it inside a test by calling TestBed.

Which is the main package for testing in Angular?

The Angular testing package includes two utilities called TestBed and async . TestBed is the main Angular utility package.

Which of the following can be used to run unit tests in Angular?

Jasmine is the default test framework used with Angular.


2 Answers

However, the test case doesn't work and throws "Error: No provider for Http!".

Because you still have the service in the providers, so Angular is trying to create it still

providers: [
 InteractWithServerService,
    ChildService],

What you can do instead of creating a mock class is to just do something like

providers: [
  { 
    provide: InteractWithServerService,
    useValue: { get: Observable.of(..) }
  }
]

Here's you're using useValue which provide any object. That will be the value used when injected. In the case above, it is just some arbitrary object with your mock method.

If you want to spy so that you can provide different values, you could inject the InteractWithServerService, and then do

spyOn(service, 'get').and.returnValue(Observable.of(...))
// do test

Another thing you could do is mock the Http with a dummy object

{ provide: Http, useValue: {} }

Now the InteractWithServerService will work (just adding the class to the providers` like you currently have). And you can just spy on it

let service = TestBed.get(InteractWithServerService);
spyOn(service, 'get').and.returnValue(..)
// do test
like image 77
Paul Samsotha Avatar answered Oct 17 '22 09:10

Paul Samsotha


Using jasmin 2.6.2: get is a function so you need to add the arrow function notation to the answer above:

providers: [
  { 
    provide: InteractWithServerService,
    useValue: { get: () => Observable.of(..) }
  }
]
like image 35
dataphile Avatar answered Oct 17 '22 08:10

dataphile