Reputation: 587
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
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
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