如何在不使用TestBed的情况下模拟Angular中的http帖子?

时间:2017-05-19 17:45:03

标签: angular unit-testing typescript jasmine rxjs

如何对此登录功能进行单元测试,特别是http post部分?我做的http模拟没有正确编码进入代码的'if ... else'部分。我不想使用TestBed。 TestBed太慢了。

login(username: string, password: string): Observable<boolean> {

    const headers = new Headers();
    headers.append('Content-Type', 'application/json');
    headers.append('accept', 'application/json');
    let options = new RequestOptions({ headers: headers });

    return this.http.post('https://blah/api/login',
      JSON.stringify({ username: username, password: password }), options)
        .map((response: Response) => {
            const token = response.json() && response.json().access_token;
            if (token) {
                this.token = token;
                localStorage.setItem('currentUser', JSON.stringify({ username: username, token: token }));
                return true;
            } else {
                return false;
            }
        }).catch(this._serverError);
}

private _serverError(err: any) {
    return Observable.throw(err || 'backend server error');
}

以下是Jasmine测试我尝试:我需要帮助这条线。

spyOn(mockHttp,'post').and.returnValue(Observable.of(response));

我的returnValue应该在登录函数中使用'if ... else'代码?

    describe('AuthenticationService', () => {
      let service: AuthenticationService;
      let mockHttp = null;

      beforeEach(() => {
        mockHttp = {};
        mockHttp.post = function(){};
        service = new AuthenticationService(mockHttp);
     });

    it(`should set access token in local storage for successful login`,() => {
      const access_token = 'blah83balc380';
      const responseOptions =  new ResponseOptions();
      responseOptions.status = 200;
      responseOptions.body = {access_token:access_token};
      const username = 'test';
      const currentUserExpected = JSON.stringify({ username: username, token: access_token });
      var response = new Response(responseOptions);
      spyOn(mockHttp,'post').and.returnValue(Observable.of(response));
      service.login(username, 'test');
      var currentUser = localStorage.getItem('currentUser');
      expect(currentUserExpected).toEqual(currentUser);
    });
  });

3 个答案:

答案 0 :(得分:1)

这是我最终使用的答案。当我可以像下面的代码一样使用TestBed时,我以一种缓慢的方式使用TestBed:

import { TestBed, inject} from '@angular/core/testing';
import { HttpModule,  XHRBackend, Response, ResponseOptions } from '@angular/http';
import { AuthenticationService } from '../_services/authentication.service';
import { MockBackend } from '@angular/http/testing';

describe('AuthenticationService', () => {
  let mockbackend, service;

  beforeEach(() => {
    localStorage.clear();
    TestBed.configureTestingModule({
      imports: [ HttpModule ],
      providers: [
        AuthenticationService,
        { provide: XHRBackend, useClass: MockBackend }
      ]
    });
  });

  beforeEach(inject([AuthenticationService, XHRBackend], (_service, 
_mockbackend) => {
    service = _service;
    mockbackend = _mockbackend;
  }));

  it('should set access token in local storage for successful login', () => 
 {
    const access_token = 'blah83balc380';
    const username = 'test';
    const currentUserExpected = JSON.stringify({ username: username, token: access_token });
    const response = {access_token: access_token};
    const responseOptions = new ResponseOptions();
    responseOptions.body = JSON.stringify(response);
    mockbackend.connections.subscribe(connection => {
      connection.mockRespond(new Response(responseOptions));
    });
    service.login(username, 'test').subscribe(respond => {
      expect(respond).toEqual(true);
      const currentUser = localStorage.getItem('currentUser');
      expect(currentUserExpected).toEqual(currentUser);
    });
  });

it('should not set access token in local storage for unsuccessful login', () =>     {
    const username = 'test';
    const responseOptions = new ResponseOptions();
    responseOptions.body = '';
    responseOptions.status = 401;
    const response = new Response(responseOptions);
    response.ok = false;
    response.statusText = 'Unauthorized';
    response.type = 2;

    mockbackend.connections.subscribe(connection => {
      connection.mockRespond(response);
    });
    service.login(username, 'test').subscribe(respond => {
      expect(respond).toEqual(false);
    }, err => {
      const currentUser = localStorage.getItem('currentUser');
      expect(currentUser).toEqual(null);
    });
  });
});

答案 1 :(得分:0)

您可以将服务注入您的测试中 它('应该做某事',注入([YourService,XHRBackend],(service:YourService,mockBackend)=&gt; {  mockBackend.connections.subscribe((连接)=&GT; {    connection.mockRespond(new Response({body:JSON.stringify('your mock response')}); ... 这将导致您的帖子调用返回该模拟数据。我不确定的是,如果你没有先做一个测试平台和提供就可以实际注入XHRBackend {提供:XHRBackend,useClass:MockBackend}

答案 2 :(得分:0)

NonEmpty通常是测试Angular服务的首选方式。

尽管官方指南说,

  

独立单元测试本身检查一个类的实例,而不依赖于Angular或任何注入的值。测试人员使用new创建类的测试实例,根据需要为构造函数参数提供测试双精度,然后探测测试实例API表面。

     

您应该为管道和服务编写独立的单元测试。

孤立的测试不能解决DI测试问题。使用TestBed实例化类时,其DI装饰器(new@Injectable)未经过测试。

@Inject测试在涉及Http时也更容易编写和维护。

当性能成为真正的问题时,某些测试可以从MockBackend转换为隔离。在这种情况下,应使用Jasmine模拟复制TestBed API。为了获得全面覆盖,应测试所有函数调用。测试看起来像

Http

然后使用没有 mockHttp = jasmine.createSpyObj(['post']); service = new AuthenticationService(mockHttp); ... it(..., fakeAsync(async () => { const bodyMock = { access_token: 'foo' }; const responseMock = { json: jasmine.createSpy().and.returnValue(bodyMock) }; const responseMock$ = Observable.of(responseMock); mockHttp.post.and.returnValue(responseMock$); const login$ = service.login(...); expect(mockHttp.post).toHaveBeenCalledTimes(1); const postArgs = callback.calls.first().args; expect(postArgs).toEqual([..., ..., jasmine.any(RequestOptions)); const requestOptions = postArgs[2]; expect(requestOptions.headers).toEqual(jasmine.any(Headers)); expect(Array.from(requestOptions.headers._headers)).toEqual([ ['Content-Type', ['application/json']], ['accept', ['application/json']] ]); expect(login$).toEqual(jasmine.any(Observable)); const login = await login$.toPromise(); expect(responseMock.json).toHaveBeenCalled(); expect(service.token).toBe('foo'); expect(localStorage.setItem).toHaveBeenCalledWith(...); expect(login).toBe(true); })); 的{​​{1}}执行另一项测试。

应该注意的是,bodyMock也应该被删除,以便进行适当的测试。出于可测试性原因,通过DI使用本地存储服务是有益的。