角单元测试模拟服务

时间:2020-07-29 17:35:09

标签: angular unit-testing jasmine

我有一个要测试的服务,并且涉及另外2个服务

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);
      })
    );
  }
}

这是我的测试课:

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();
  });
});

我的问题是我的测试根本没有通过。我的印象是问题来自我的模拟 我不能嘲笑我的两项服务

这是我的错误:

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)

我的EnvService负载配置和UserApiService包含metbod如何使用httpClient调用api 我的用户是Angular 9


我要更新测试:

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

但是我有这个错误:

类型'EnvConfiguration'缺少类型'{_isScalar的以下属性:ExpectedRecursive;来源:ExpectedRecursive ;运算符:ExpectedRecursive >; ... 6更多...; toPromise:ExpectedRecursive <...>; }':_ isScalar,来源,操作员,提升和另外6个。

2 个答案:

答案 0 :(得分:0)

您的测试未正确注入UserApiService,因此,当您调用getUserList()时,它将尝试启动未定义的UserApiService.getUsers()

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*', () => {
  });
});

答案 1 :(得分:0)

我认为您做错了一些事情,一开始缺少一项服务,UserApiService,其次您不需要TestBed来测试可以创建的服务它的一个新实例并将模拟内容注入其中。我也不知道您为什么需要这个HttpTestingController?在您的服务中无处可寻,最后您不应该检查返回了什么负载,但是一旦服务初始化/创建后就调用了它……本质上,我已经使用了您的代码,对其进行了一些重组在您想做的事情中,有更多建议可为您提供示例,但这应该为您提供了一个良好的开端。

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();
            });
        })
    })
});