4

I have a service that I’m trying to test and that refers to 2 other services

export class UserService { private env: EnvConfiguration; constructor(private userApiService: UserApiService, private envService: EnvService) { this.envService.load().subscribe(env => { this.env = env; }); this.userApiService.rootUrl = this.env.apiUrl; } getUserList(): Observable<User[]> { return this.userApiService.getUsers().pipe( map(result => result), catchError(err => { return throwError(err); }) ); } } 

And this is my test class :

 describe('UserService', () => { let service: UserService; let httpMock: HttpTestingController; let envServiceSpy = jasmine.createSpyObj('EnvService', ['load']); beforeEach(() => { TestBed.configureTestingModule({ imports: [HttpClientTestingModule], providers: [UserService, { provide: EnvService, useValue: envServiceSpy }], }); service = TestBed.inject(UserService); httpMock = TestBed.inject(HttpTestingController); envServiceSpy = TestBed.inject(EnvService) as jasmine.SpyObj<EnvService>;; }); afterEach(inject([HttpTestingController], (httpMock: HttpTestingController) => { httpMock.verify(); })); it('should be created', () => { const stubValue = "apiUrl: 'http://'"; envServiceSpy.load.and.returnValue(of(stubValue)); expect(service).toBeTruthy(); expect(envServiceSpy.load.calls.mostRecent().returnValue) .toBe(stubValue); }); it('should return value from observable', () => { expect(this.service.getUserList()).toBeTruthy(); }); }); 

My problem is that my tests do not pass at all. I have the impression that the problem comes from my mocks I can’t mock my two services

This is my error :

UserService > should be created TypeError: Cannot read property 'subscribe' of undefined at <Jasmine> at new UserService (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.ts:15:27) at Object.UserService_Factory [as factory] (ng:///UserService/ɵfac.js:5:10) at R3Injector.hydrate (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:17193:42) at R3Injector.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:16943:1) at NgModuleRef$1.get (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:36329:1) at TestBedRender3.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3227:1) at Function.inject (http://localhost:9876/_karma_webpack_/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:3110:1) at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:22:23) at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1) at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1) Expected undefined to be truthy. Error: Expected undefined to be truthy. at <Jasmine> at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:36:21) at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1) at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1) Expected undefined to be 'apiUrl: 'gttp://''. Error: Expected undefined to be 'apiUrl: 'gttp://''. at <Jasmine> at UserContext.<anonymous> (http://localhost:9876/_karma_webpack_/src/app/services/user/user.service.spec.ts:38:6) at ZoneDelegate.invoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-evergreen.js:364:1) at ProxyZoneSpec.push../node_modules/zone.js/dist/zone-testing.js.ProxyZoneSpec.onInvoke (http://localhost:9876/_karma_webpack_/node_modules/zone.js/dist/zone-testing.js:292:1) 

My EnvService load configuration and my UserApiService contains metbod how call api with httpClient I'm user Angular 9


I'm update my test :

it('should be created', () => { let envConfig: EnvConfiguration; envServiceSpy.load.and.returnValue(of(envConfig)); expect(service).toBeTruthy(); expect(envServiceSpy.load.calls.mostRecent().returnValue) .toBe(envConfig); }); 

But I have this error :

Type 'EnvConfiguration' is missing the following properties from type '{ _isScalar: ExpectedRecursive<boolean>; source: ExpectedRecursive<Observable<any>>; operator: ExpectedRecursive<Operator<any, EnvConfiguration>>; ... 6 more ...; toPromise: ExpectedRecursive<...>; }': _isScalar, source, operator, lift, and 6 more. 
3
  • if they don't pass, tell us why. It could help to find the root cause. Commented Jul 29, 2020 at 18:22
  • 1
    I'm update my answer Commented Jul 29, 2020 at 18:40
  • what do you need this for let httpMock: HttpTestingController;? the only providers i see are the UserApiService and the EnvService Commented Jul 30, 2020 at 1:14

2 Answers 2

3

Your test does not inject the UserApiService properly, thus when you call getUserList() it attempts to start UserApiService.getUsers() which is not defined.

import createSpyObj = jasmine.createSpyObj; import SpyObj = jasmine.SpyObj; import {of} from 'rxjs'; // .. Other imports describe('UserService', () => { let service: UserService; let envServiceSpy: SpyObj<EnvService>; let userApiService: SpyObj<UserApiService>; let usersMock = [ {id: 1, name: 'Walter White', bestQuote: 'I am the one who knocks.'}, {id: 2, name: 'Jesse Pinkman', bestQuote: 'Yeah, bitch! MAGNETS!'}, ]; let envMock = { apiUrl: 'http://example.com', }; beforeEach(() => { // It is a good idea to re-initiate the spy instance after each run so you do not face any weird side-effects. // That way you also do not need to call `mySpy = TestBed.inject(MyService);` envServiceSpy = createSpyObj('EnvService', ['load']); envServiceSpy.load.and.returnValue(of(envMock)) userApiService = createSpyObj('UserApiService', ['getUsers'], ['rootUrl']); userApiService.getUsers.and.returnValue(of(usersMock)); TestBed.configureTestingModule({ providers: [ UserService, {provide: EnvService, useValue: envServiceSpy}, {provide: UserApiService, useValue: userApiService}, ], }); service = TestBed.inject(UserService); }); it('should be created', () => { expect(service).toBeTruthy(); }); it('should set rootUrl for userApiService on init', () => { // Considering the `constructor()` did run already due to our initialization in `beforeEach()` // we can just assert on our expectations expect(envServiceSpy.load).toHaveBeenCalled(); expect(userApiService.rootUrl).toEqual('http://example.com'); }); // Here we test, that the `getUserList()` method in fact mapped // the (mocked) response from `getUsers()` properly it('should retrieve user list ', (done) => { service.getUserList().subscribe((userList) => { expect(userList).toEqual(usersMock); expect(userApiService.getUsers).toHaveBeenCalled(); done(); }, done.fail); }); xit('TODO: Write a test that performs the call to `getUsers()` which returns an *error*', () => { }); }); 
Sign up to request clarification or add additional context in comments.

4 Comments

envServiceSpy.load.and.returnValue must return an object in the end and not a string
Given you did not post any types I of course had to imagine of some kind. You should edit this snippet to your needs.. the question that you had in the first place should be answered though.
I've updated my answer. It should do the job, given the problems you explained.
I modified my tests but I still have mistakes: Test should set rootUrl for userApiService on init' : Error: Expected spy EnvService.load to have been called., Error: Expected undefined to equal 'api'. Test should return value from observable : service.getUserList() is not a function
0

I think you're doing a few things wrong, you're missing a service to begin with, the UserApiService, second you don't need TestBed to test a service you can just create a new instance of it and inject the mocks into it. I also don't know why you need this HttpTestingController? is nowhere in your service and lastly you shouldn't be checking what load has returned but that it has been called once the service is initialised/created whatever..., in essence I've taken your code, restructure it a little and sort of guessed what you were trying to do, there is more advice to give you as to your example but this should give you a head start.

describe('UserService', () => { let service: UserService; let mockUserApiService: jasmine.SpyObj<UserApiService>; let mockEnvService: jasmine.SpyObj<EnvService>; const stubValue = "apiUrl: 'http://'"; beforeEach(() => { mockUserApiService = jasmine.createSpyObj('UserApiService', ['getUsers', 'rootUrl']); mockEnvService = jasmine.createSpyObj('EnvService', ['load', 'apiUrl']); }); describe('when the service is created', () => { beforeEach(() => { mockUserApiService.load.and.returnValue(of(stubValue)); mockEnvService.rootUrl = 'https://some-site.com'; service = new UserService(mockUserApiService, mockEnvService); }); it('should call the environment service', () => { expect(mockEnvService.load).toHaveBeenCalledTimes(1); }); describe('getUserList', (done) => { const users = [] as Users[]; beforeEach(() => { mockUserApiService.getUsers.and.returnValue(of(users)); service.getUserList(); }); it('should call the users api service', () => { expect(mockUserApiService.getUsers).toHaveBeenCalled(); }); }) }) }); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.