简体   繁体   中英

Testing an Angular service method which returns an Observable

I'm trying to get all lines green on the following Angular 12.6 service: I have this test group

describe('AuthService', () => {
  let service: AuthService;
  let apiResponse: ApiResponse;
  let controller: HttpTestingController;
  let accountDetails: AccountDetails;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        {
          provide: HTTP_INTERCEPTORS,
          useClass: AuthInterceptor,
          multi: true,
        },
        { provide: APP_CONFIG, useValue: AppConfig },
      ],
    });
    service = TestBed.inject(AuthService);
    controller = TestBed.inject(HttpTestingController);
    apiResponse = {
      accessToken: 'Gimme access'
    };
    accountDetails = {
      Password: 'bla',
      UserName: 'vlad',
    };
  });

/// a few other tests below
  it('should call the back-end', () => {
    spyOn(service, 'setSession');
    spyOn(service, 'login').and.callThrough();

    service.login(accountDetails.UserName, accountDetails.Password).subscribe(() => {
      expect(service.setSession).toHaveBeenCalled();
    });

    expect(service.login).toHaveBeenCalledOnceWith('vlad', 'bla');

    controller.match(AppConfig.apiEndPoint);
  });
});

And this as service method:

@Injectable({
  providedIn: 'root',
})
export class AuthService {
  #storage = localStorage;
  options = new HttpHeaders({
    'Content-Type': 'application/json',
  });

  private static handleError(error: HttpErrorResponse) {
    if (error.status === 0) {
      // A client-side or network error occurred. Handle it accordingly.
      console.error('An error occurred:', error.error);
    } else {
      // The backend returned an unsuccessful response code.
      // The response body may contain clues as to what went wrong.
      console.error(`Backend returned code ${error.status}, body was: `, error.error);
    }
    // Return an observable with a user-facing error message.
    return throwError('Something happened; please try again later.');
  }

  constructor(@Inject(APP_CONFIG) private config: IapConfig, private http: HttpClient) {}

  login(customerKey: string, username: string, password: string): Observable<any> {
    const timestamp = new Date().getTime();

    return this.http
      .post<AccountDetails>(this.config.apiEndPoint, {
        Password: password,
        UserName: username,
      })
      .pipe(catchError(AuthService.handleError))
      .pipe(map((response: AccountDetails) => {
        this.setSession(response);
        return response;
      }))
  }

  setSession(data: ApiResponse | any): void {
    try {
      // tslint:disable-next-line:no-console
      console.info('User session is set 😁');
    } catch (error) {
      throw new Error(`Incorrect response structure. Unable to map: ${error}`);
    }
  }
}


One: Somehow I have the feeling I'm not really testing the setSession method invocation from the login method. And Two how can I test the line in the catch block?

How I can mock to test without mocking the Observable returned by the login though the pipe operator. The setSession under the subscription of the login method I can't mock and check either is called with the right arguments.

After reading the recommended by @AliF50 article from the Testing Angular - Testing a Service for which I thank him. I come up with the following in my test files:

import { TestBed } from '@angular/core/testing';

import { AuthService } from './auth.service';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { APP_CONFIG, AppConfig } from '../app.config';
import { AccountDetails, ApiResponse } from '../interfaces';

describe('AuthService', () => {
  let service: AuthService;
  let apiResponse: ApiResponse;
  let controller: HttpTestingController;
  let accountDetails: AccountDetails;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule],
      providers: [
        AuthService,
        { provide: APP_CONFIG, useValue: AppConfig },
      ],
    });
    service = TestBed.inject(AuthService);
    controller = TestBed.inject(HttpTestingController);
    apiResponse = {
      accessToken: 'Gimme access',
      languageCode: 'EN',
    };
    accountDetails = {
      Password: 'bla',
      UserName: 'vlad',
      TimeStamp: '123454',
    };
  });


  it('should call the back-end', () => {
    spyOn(service, 'setSession');
    spyOn(service, 'login').and.callThrough();
    let result: any;

    service.login(accountDetails.UserName, accountDetails.Password)
      .subscribe((response) => {
        result = response;
        expect(service.setSession).toHaveBeenCalled();
        expect(service.setSession).toHaveBeenCalledWith(apiResponse);
        expect(result).toEqual(apiResponse);
    });

    const request = controller.expectOne(AppConfig.apiEndPoint);
    request.flush(apiResponse);
  });
});

Some key elements I missed:

  • I missed injecting the service
  • calling request.flush and testing the request

I also changed a bit the login component and its call to the service without a subscription. Instead of, the service is calling setSession on successful response. I've edited the question. Once again - thank you @AliF50 for this precious and unofficial resource~!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM