1

this is my first unit test in angular 7, i want to test my methode inside my service, this methode return Observable:

fetchData(chronoInfo: ALChrono): Observable<any> {
        // construct API parameters and URL
        var URL: string = this.urlDecoratorService.urlAPIDecorate("AL", "GetAccessChrono");

        var params = this.urlDecoratorService.generateParameters({
            year: chronoInfo.year,//Année
            month: chronoInfo.month,//Mois (peut être null)
            sortBy: chronoInfo.sortBy,// Champ de tri
            sortDirection: chronoInfo.sortDirection,//True pour inverser l'ordre de tri
            pageNumber: chronoInfo.currentPage,//Page de résultats
            pageSize: chronoInfo.pageSize//Nb enregistrements par page
        });

        return this.apiFetcher.fetchJson(URL, params);
        //retourne les AL par année & mois
    }

and the test of this methode is :

import { TestBed, async, inject } from '@angular/core/testing';
import { AnnonceChronoDetailService } from './annonce-chrono-detail.service';
import { HttpClientModule } from '@angular/common/http';
import { UrlDecoratorService } from "src/app/common/url-decorator.service";
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { APIFetcherService } from "src/app/services/common/api-fetcher.service";
import { Http, ConnectionBackend, RequestOptions, HttpModule } from "@angular/http";
import { ALChrono } from '../../common/IALChrono.interface';
import { Observable } from 'rxjs';

describe('AnnonceChronoDetailService', () => {
    let service: AnnonceChronoDetailService;
    let alChrono: ALChrono = { //it's an interface'
        year: 2012,
        month: -1,
        sortBy: 'asc',
        sortDirection: true,
        currentPage: 1,
        pageSize: 15
    }

    beforeEach(() => {
        TestBed.configureTestingModule({
            imports: [HttpModule],
            providers: [
                AnnonceChronoDetailService,
                UrlDecoratorService,
                APIFetcherService,
                Http,
                ConnectionBackend]
        });
    });


    it('should be created', inject([AnnonceChronoDetailService], (service: AnnonceChronoDetailService) => {
        expect(service).toBeTruthy();
    }
    ));


    it('#fetchData should return value from observable', (done: DoneFn) => {
        service.fetchData(alChrono).subscribe(value => {
            expect(value).toBe('observable value');
            done();
        });
    });

});

and when i execute ng test i have problem in the seconde test #fetchData should return value from observable , the error is :

AnnonceChronoDetailService > #fetchData should return value from observable


TypeError: Cannot read property 'fetchData' of undefined
    at <Jasmine>
    at UserContext.<anonymous> (http://localhost:9876/src/app/services/annonce-legale/annonce-chrono-detail.service.spec.ts?:41:17)
    at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:9876/node_modules/zone.js/dist/zone.js?:388:1)
    at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/node_modules/zone.js/dist/zone-testing.js?:288:1)
    at ZoneDelegate../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke (http://localhost:9876/node_modules/zone.js/dist/zone.js?:387:1)
    at Zone../node_modules/zone.js/dist/zone.js.Zone.run (http://localhost:9876/node_modules/zone.js/dist/zone.js?:138:1)
    at runInTestZone (http://localhost:9876/node_modules/zone.js/dist/zone-testing.js?:506:1)
    at UserContext.<anonymous> (http://localhost:9876/node_modules/zone.js/dist/zone-testing.js?:522:1)
    at <Jasmine>

i try some solutions like adding asyn but the problem was the same.

Gonçalo Peres
  • 11,752
  • 3
  • 54
  • 83
Saad
  • 346
  • 4
  • 20
  • Remove the unused variable and the error will become obvious. In general, don't write code like that. Instead, follow proper practices like 'declare variables at the site of first use". – Aluan Haddad Nov 02 '18 at 12:44
  • @AluanHaddad I make some change for declaration, but I think all variables are used. – Saad Nov 02 '18 at 13:34

1 Answers1

0

You define 'service' in the first spec ('it' function) but then try and use it in the second. If you want to use the 'service' variable within each spec, which makes sense, then I would suggest you put something like this inside the beforeEach function to define service correctly for every spec:

service = TestBed.get(AnnonceChronoDetailService);

Then change your first spec to the following:

it('should be created', () => {
    expect(service).toBeTruthy();
});

Now your second spec should work as written.

Update:

Adding spies for your other services. Many ways to do this. Here is one:

describe('AnnonceChronoDetailService', () => {
    const spyDecorator = jasmine.createSpyObj('UrlDecoratorService', {
        urlAPIDecorate: '/test/url',
        generateParameters: null /* set test params to return here /*
    };
    const spyFetcher = jasmine.createSpyObj('APIFetcherService', {
        fetchJson: {}
    };
    let service: AnnonceChronoDetailService;
    ...

Now change your providers array to use the spies instead of the original services:

        providers: [
            AnnonceChronoDetailService,
            { provide: UrlDecoratorService, useValue: spyDecorator } ,
            { provide: APIFetcherService, useValue: spyFetcher },
            ]

and then in your spec files you can check those spies. Change these suggestions to something you actually care about:

it('#fetchData should return value from observable', () => {
    spyFetcher.fetchJson.and.returnValue(of('observable value'));
    service.fetchData(alChrono).subscribe(value => {
        expect(value).toEqual('observable value');
    });
    expect(spyDecorator.urlAPIDecorate).toHaveBeenCalled();
    expect(spyDecorator.generateParameters).toHaveBeenCalledTimes(1);
    expect(spyFetcher.fetchJson).toHaveBeenCalledWith('/test/url', /* test params defined above */);
});
dmcgrandle
  • 5,934
  • 1
  • 19
  • 38
  • i do this and i have this error **TypeError: backend.createConnection is not a function** – Saad Nov 02 '18 at 16:30
  • i add **BrowserModule** in imports and i delet **http** and **ConnectionBackend** from providers but i have this problem **Uncaught Response with status: 0 for URL: null** – Saad Nov 02 '18 at 16:33
  • I suggest you add spies for both your APIFetcherService and UrlDecoratorService so that the real functions aren't called - just test that they were called properly. – dmcgrandle Nov 02 '18 at 16:42
  • i set **null** in **generateParameters** and **toHaveBeenCalledWith('/test/url', null);** and its work but i don't understand this structer exactlly, and i'm not sure if this test work as i want. – Saad Nov 05 '18 at 16:38
  • Okay, what exactly are you trying to accomplish? If you don’t understand how spies work, I would suggest reading the jasmine documentation. There are many good blog posts that also discuss the basics of testing such as https://medium.com/frontend-fun/angular-unit-testing-jasmine-karma-step-by-step-e3376d110ab4 – dmcgrandle Nov 05 '18 at 17:27
  • i want to test if my methode in my service return an Observable or not,the solution that you give me works perfectly but when there is no data the test works, i don't know why. – Saad Nov 05 '18 at 17:33
  • The test I showed actually tests the value returned from the observable, so I really don't understand what you mean. I have another suggestion for you. Here is a Stackblitz I set up for another question on StackOverflow which is similar to yours. Fork this to your own account and set up with your own data. Then comment back with a link to what you are seeing and I'll try to help. Stackblitz is here: https://stackblitz.com/edit/stackoverflow-q-53102348?file=app%2Fmy.service.spec.ts – dmcgrandle Nov 05 '18 at 21:42
  • I understood, and my test run correctly, thanks for your patience. – Saad Nov 07 '18 at 14:24
  • I have another question for you :) , can i access to the real value that the method return? – Saad Nov 07 '18 at 17:03
  • can you get a look : [stackblitz.com/edit](https://stackblitz.com/edit/stackoverflow-q-53102348-3hee2d) – Saad Nov 07 '18 at 17:46
  • I looked at what you have in the stackblitz - I think you are asking to get access to the data that the API actually sends back, correct? If so, then NO - that is not the purpose of unit testing (also known as functional testing). To use the real data would be an end-to-end test, using protractor or such tooling. See a good discussion of the difference between the two here: https://stackoverflow.com/questions/48762575/difference-between-functional-test-and-end-to-end-test – dmcgrandle Nov 07 '18 at 19:57
  • please can you my problem [first post](https://stackoverflow.com/questions/53335638/e2e-testing-angular-7-failed-cannot-read-property-fetchdata-of-undefined) , [second post](https://stackoverflow.com/questions/53337670/angular-e2e-testing-how-to-test-service-that-have-to-other-services) – Saad Nov 16 '18 at 12:23
  • Sorry, I haven’t gone deep with Protractor yet. – dmcgrandle Nov 17 '18 at 08:51