how do I mock out http post in Angular without using TestBed?

前端 未结 3 1877
一向
一向 2021-01-17 05:25

How do I unit test this login function, specifically the http post part? The http mock I made is not coded correctly to get into the \'if...else\' section of the code. I d

相关标签:
3条回答
  • 2021-01-17 05:32

    This is the answer that I ended up using. I was using TestBed in a way that was slow when I could have been using TestBed like the code below:

    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);
        });
      });
    });
    
    0 讨论(0)
  • 2021-01-17 05:41

    You can inject services into your test like

    it('should do something',inject([YourService,XHRBackend],(service:YourService,mockBackend)=>{
     mockBackend.connections.subscribe((connection)=>{
       connection.mockRespond(new Response({body:JSON.stringify('your mock response')});
    

    ...

    this will cause your post call to return with that mock data. What I am not certain of though is if you can actually inject XHRBackend without first doing a testbed and providing

    {provide: XHRBackend, useClass:  MockBackend}
    
    0 讨论(0)
  • 2021-01-17 05:50

    TestBed is generally preferable way to test Angular services.

    Despite what the official guide says,

    Isolated unit tests examine an instance of a class all by itself without any dependence on Angular or any injected values. The tester creates a test instance of the class with new, supplying test doubles for the constructor parameters as needed, and then probes the test instance API surface.

    You should write isolated unit tests for pipes and services.

    isolated tests don't address DI testing. When a class is instantiated with new, its DI decorators (@Injectable, @Inject) are not tested.

    Http tests are also easier to write and maintain when MockBackend is involved.

    When performance becomes a real concern, some tests can be converted from TestBed to isolated. In this case Http API should be replicated with Jasmine mocks. In order to get full coverage, all functions calls should be tested. The test will look like

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

    Then another test is performed with bodyMock that doesn't have access_token.

    It should be noticed that localStorage should be stubbed as well in order to be properly tested. For testability reasons it's beneficial to use local storage service via DI instead.

    0 讨论(0)
提交回复
热议问题