Niranjan
Niranjan

Reputation: 587

How to write unit test case for api failure in angular 5?

Hi I am writing unit test case for Angular 5 application. I am consuming api's in my project. I am writing unit test case for both positive and negative scenarios. I have completed for positive cases. I am struggling in writing negative test cases. For example I have ngoninit method which loads data to grid. Below is my code snippet.

 ngOnInit() {
        this.loadTenantsData();
        this.columns = [
            { prop: "index", name: '#', width: 30, headerTemplate: this.statusHeaderTemplate, cellTemplate: this.statusTemplate, resizeable: false, canAutoResize: false, sortable: false, draggable: false },
            { prop: 'tenantname', name: 'Tenant', cellTemplate: this.nameTemplate, width: 200 },
            { name: '', width: 80, cellTemplate: this.actionsTemplate, resizeable: false, canAutoResize: false, sortable: false, draggable: false }
        ];
    }

Below is loadTenantsData method.

 private loadTenantsData() {
        this.tenantService.getTenants().subscribe(results => this.onTenantDataLoadSuccessful(results), error => this.onTenantDataLoadFailed(error));
    }

Below is my tenant service.

 getTenants(page?: number, pageSize?: number) {
        return this.tenantEndpoint.getTenantsEndpoint<Tenant[]>(page, pageSize);
    }

Below is my tenant endpoint service.

   getTenantsEndpoint<T>(page?: number, pageSize?: number): Observable<T> {
        return Observable.create(observer => {
            var tenants = [{
                'tenantid': 'bcdaedf3-fb94-45c7-b6a5-026ca4c53233',
                'tenantname': 'BENZAAD.onmicrosoft.com'
            }
            ];
            if (!tenants) {
                throw 'no tenants given'
            }
            observer.next(tenants);
        });
    }

Below is my error handler.

 private onTenantDataLoadFailed(error: any) {
        if (typeof error.error.title != 'undefined') {
            this.alertService.stopLoadingMessage();
            this.alertService.showStickyMessage("Load Error", `Unable to retrieve tenant data from the server.\r\nErrors: "${Utilities.getHttpResponseMessage(error)}"`,
                MessageSeverity.error, error);
            this.rows = [];
            this.loadingIndicator = false;
            this.alertService.showMessage(error.error.title, error.error.status, MessageSeverity.error);
        }
    }

I have put all the unit test case file below.

describe('Component: TenantEditorComponent', () => {

let component: TenantEditorComponent;
let fixture: ComponentFixture<TenantEditorComponent>;
let submitEl: DebugElement;
let el: HTMLElement;
let scopename: DebugElement;
let scopeObject;

const mockResults = { /* whatever your results should look like ... */ };
const spyTenantService = jasmine.createSpyObj({ getTenants: of(mockResults), });
const spyAlertService = jasmine.createSpyObj({
    stopLoadingMessage: null,
    showStickyMessage: null,
    showMessage: null
});

beforeEach(async(() => {
    TestBed.configureTestingModule({
        imports: [
            BrowserAnimationsModule,
            HttpClientModule,
            RouterTestingModule,
            TranslateModule.forRoot({
                loader: {
                    provide: TranslateLoader,
                    useClass: TranslateLanguageLoader
                }
            }),
            NgxDatatableModule,
            FormsModule,
            UiSwitchModule,
            TooltipModule.forRoot(),
            ModalModule.forRoot(),
            SimpleNotificationsModule.forRoot(),
            HttpClientTestingModule
        ],
        declarations: [
            TenantEditorComponent,
            SearchBoxComponent
        ],
        providers: [
            {
                provide: LogMessages, useClass: LogMessagesMock
            },
            HtmlEncoding,
            {
                provide: Adal5Service, useClass: MockAdal5Service
            },
            TenantService,
            UnitTestStorageOperations, TenantEndpoint,
            TenantsEndpointMock,
            AlertService,
            AppContextService,
            EndpointFactory,
            NotificationsService,
            AppTranslationService,
            ConfigurationService,
            LocalStoreManager,
            {
                provide: TenantEndpoint, useClass: TenantsEndpointMock
            },
            { provide: TenantService, useValue: spyTenantService }
        ]
    }).compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(TenantEditorComponent);
    component = fixture.componentInstance;
});

it('ngOnInit should call onTenantDataLoadFailed() in case of error', () => {
    var error = {
        error: {
            title: 'Tenant already exists',
            status: '409'
        }
    }
    spyOn(component, 'onTenantDataLoadFailed').and.callThrough();
    debugger;
    spyTenantService.getTenants.and.returnValue(ErrorObservable.create({ error }));
    fixture.detectChanges();
    expect(spyTenantService.getTenants).toHaveBeenCalledTimes(1);
    expect(spyAlertService.stopLoadingMessage).toHaveBeenCalled();
    expect(component.onTenantDataLoadFailed).toHaveBeenCalled();
    expect(spyAlertService.showStickyMessage).toHaveBeenCalled();
    expect(spyAlertService.showMessage).toHaveBeenCalled();
});

For example, for any reason API may file. In that case my error handler will be called. I want to write unit test case for this scenario. Can someone help me to write unit test case for negative scenario? Any help would be appreciated. Thank you.

Upvotes: 2

Views: 3542

Answers (2)

dmcgrandle
dmcgrandle

Reputation: 6060

The failure (negative) scenario can be tested right after the successful (positive) one. There are many ways to accomplish this and you did not supply what your test (spec) file looks like so far. I would start by creating spies for the services, then adding them in to the providers for your TestBed.

Another key is NOT to call fixture.detectChanges() until you have set up either the success or failure spy.returnValue()'s, so wait and do this within the it() spec definitions.

Something like this:

import { of, throwError } from 'rxjs';

const mockResults = { /* whatever your results should look like ... */ };
const spyTenantService = jasmine.createSpyObj({getTenants: of(mockResults),});
const spyAlertService = jasmine.createSpyObj({
  stopLoadingMessage: null,
  showStickyMessage: null,
  showMessage: null
});
let component: MyComponent;
let fixture: ComponentFixture<MyComponent>;

beforeEach(async(() => {
    TestBed.configureTestingModule({
        declarations: [MyComponent],
        imports: [ ],
        providers: [
            { provide: TenantService, useValue: spyTenantService },
            { provide: AlertService, useValue: spyAlertService },
        ]
    })
    .compileComponents();
}));

beforeEach(() => {
    fixture = TestBed.createComponent(MyComponent);
    component = fixture.componentInstance;
});

it('ngOnInit should initialize properly in positive (no error) case', () => {
    fixture.detectChanges();
    expect(component.results).toEqual(mockResults); // Something like this ...
    /* I have no idea what this should look like - you said you have it working */
});
it('ngOnInit should call onTenantDataLoadFailed() in case of error', () => {
  spyOn(component, 'onTenantDataLoadFailed').and.callThrough();
  spyTenantService.getTenants.and.returnValue(throwError({error: {title: 'defined'}}));
  fixture.detectChanges();
  expect(spyTenantService.getTenants).toHaveBeenCalledTimes(1);
  expect(spyAlertService.stopLoadingMessage).toHaveBeenCalled();
  expect(component.onTenantDataLoadFailed).toHaveBeenCalled();
  expect(spyAlertService.showStickyMessage).toHaveBeenCalled();
  expect(spyAlertService.showMessage).toHaveBeenCalled();
});

Update:

Thank you for providing your spec file. As I mentioned in a comment above, you should DELETE the existing entries you have in your providers array for TenantService and AlertService because you'll be providing those via a spy instead of with the original services. You also forgot to include the line:

{ provide: AlertService, useValue: spyAlertService }

So the resulting providers array should look something like this:

providers: [
    { provide: LogMessages, useClass: LogMessagesMock },
    HtmlEncoding,
    { provide: Adal5Service, useClass: MockAdal5Service },
    UnitTestStorageOperations,
    AppContextService,
    EndpointFactory,
    NotificationsService,
    AppTranslationService,
    ConfigurationService,
    LocalStoreManager,
    { provide: TenantEndpoint, useClass: TenantsEndpointMock },
    { provide: TenantService, useValue: spyTenantService },
    { provide: AlertService, useValue: spyAlertService }
]

Note: I also deleted TenantEndpoint and TenantsEndpointMock from earlier in the array since you specify it later - you only want one entry for each provider you are injecting into your component. But ... I don't see a definition for TenantsEndpointMock, so I'm guessing that is either declared before the snippet you pasted above, or else is giving you an error as well. :)

Upvotes: 2

JBoothUA
JBoothUA

Reputation: 3149

I think this should be able to help you, let me know if you have any issues:

import { async, ComponentFixture, TestBed, fakeAsync, flush } from '@angular/core/testing';
import { ChatPageComponent } from './chat-page.component';
import { FormsModule } from '@angular/forms';
import { UserService } from '../user.service';
import { UserMockService } from '../test/user-mock.service';
import { HubService } from '../hub.service';
import { HubMockService } from '../test/hub-mock.service';
import { CommonHttpService } from '@matrixtools/common';
import { HttpMockService } from '../test/http-mock.service';

describe('ChatPageComponent', () => {
  let component: ChatPageComponent;
  let fixture: ComponentFixture<ChatPageComponent>;

  beforeEach(async(() => {
    TestBed.configureTestingModule({
      declarations: [ChatPageComponent],
      providers: [{ provide: UserService, useClass: UserMockService },
        { provide: HubService, useClass: HubMockService },
        { provide: CommonHttpService, useClass: HttpMockService }],
      imports: [FormsModule]
    })
    .compileComponents();
  }));

  beforeEach(() => {
    fixture = TestBed.createComponent(ChatPageComponent);
    component = fixture.componentInstance;
    fixture.detectChanges();
  });

  it('should create', () => {
    expect(component).toBeTruthy();
  });

  describe('ping function', () => {
    it('should append a green message if hub connects', fakeAsync(() => {
      // arrange
      spyOn(component.hubConnection, 'invoke').and.returnValue(Promise.resolve('good promise'));
      let expectedColor = 'green';
      component.messages = [];

      // act
      component.ping();
      flush();

      // assert
      let actualColor = component.messages[1].color;
      expect(actualColor).toBe(expectedColor);
    }));

    it('should append a red message if hub connection fails', fakeAsync(() => {
      // arrange
      spyOn(component.hubConnection, 'invoke').and.returnValue(Promise.reject('bad promise'));
      let expectedColor = 'red';
      component.messages = [];

      // act
      component.ping();
      flush();

      // assert
      let actualColor = component.messages[1].color;
      expect(actualColor).toBe(expectedColor);
    }));
  });
});

Upvotes: 1

Related Questions