Reputation: 1085
I have written the following unit test for my Angular 10 component, which basically displays a tree view with some interactivity:
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ReactiveFormsModule } from '@angular/forms';
// import { MatFormFieldModule } from '@angular/material/form-field';
// import { MatInputModule } from '@angular/material/input';
import { MatProgressBarModule } from '@angular/material/progress-bar';
import { MatProgressSpinnerModule } from '@angular/material/progress-spinner';
import { BrowserModule } from '@angular/platform-browser';
import { TreeviewModule } from 'ngx-treeview';
import { DocumentTreeService } from '../services/DocumentTreeService';
import { DocumentTreeviewComponent } from './document-treeview.component';
describe('DocumentTreeviewComponent', () => {
let component: DocumentTreeviewComponent;
let fixture: ComponentFixture<DocumentTreeviewComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DocumentTreeviewComponent ],
imports: [ TreeviewModule.forRoot(), ReactiveFormsModule, MatProgressBarModule,
BrowserModule, MatProgressSpinnerModule, /* MatFormFieldModule, MatInputModule */ ],
providers: [ DocumentTreeService ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DocumentTreeviewComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
My DocumentTreeService.ts:
import { HttpClient } from '@angular/common/http';
import { TreeviewItem } from 'ngx-treeview';
import { GlobalDataService } from './global-data.service';
interface TreeItem {
name?: string;
text: string;
value: any;
children?: String[];
type: 'folder' | 'document';
}
/* tslint:disable no-string-literal prefer-for-of */
export class DocumentTreeService {
constructor(public http: HttpClient, public DataService: GlobalDataService){}
public treeviewmtextresponse;
public docNames = [];
public FolderItems = [];
public treeviewItem = [];
public finalTreeviewElements: TreeviewItem[] = [];
getDocItems(): TreeviewItem[] {
const docItems = [];
// TEMPORARY SOLUTION (QUICKFIX) FOR 1 DOC
if (!this.treeviewmtextresponse.length) {
const element = this.treeviewmtextresponse['documentName'];
this.docNames.push(element);
const tempArray = element.split('!');
if (!this.FolderItems.includes(tempArray[0])) {
this.FolderItems.push(tempArray[0])
}
docItems[tempArray[0]] = this.docNames.filter(s => s.includes(tempArray[0] + '!'));
for (let o = 0; o < docItems[tempArray[0]].length; o++) {
docItems[tempArray[0]][o] = docItems[tempArray[0]][o].replace(tempArray[0] + '!', '');
const documentItem: TreeItem = {
text: docItems[tempArray[0]][o],
type: 'document',
value: docItems[tempArray[0]][o],
}
docItems[tempArray[0]][o] = documentItem;
}
}
// TEMPORARY SOLUTION (QUICKFIX) FOR 1 DOC
for (let m = 0; m < this.treeviewmtextresponse.length; m++) {
const element = this.treeviewmtextresponse[m]['documentName'];
this.docNames.push(element);
const tempArray = element.split('!');
if (!this.FolderItems.includes(tempArray[0])) {
this.FolderItems.push(tempArray[0])
}
docItems[tempArray[0]] = this.docNames.filter(s => s.includes(tempArray[0] + '!'));
for (let o = 0; o < docItems[tempArray[0]].length; o++) {
docItems[tempArray[0]][o] = docItems[tempArray[0]][o].replace(tempArray[0] + '!', '');
const documentItem: TreeItem = {
text: docItems[tempArray[0]][o],
type: 'document',
value: docItems[tempArray[0]][o],
}
docItems[tempArray[0]][o] = documentItem;
}
}
let jsonString;
for (let p = 0; p < this.FolderItems.length; p++) {
const element = this.FolderItems[p];
const treeItem: TreeItem = {
name: element + "!" + docItems[element][0]["text"],
text: element,
value: element,
type: 'folder',
children: docItems[element],
}
const DokData = treeItem["name"];
this.DataService.SelectedDocumentList.push(DokData);
jsonString = JSON.stringify(treeItem);
const finalObject: TreeviewItem = new TreeviewItem(
JSON.parse(jsonString));
this.finalTreeviewElements.push(finalObject);
}
return this.finalTreeviewElements;
}
}
My full error message:
Error: Can't resolve all parameters for DocumentTreeService: (?, ?).
I originally thought I messed up with placing the service in the correct category but with trial and error and some logic I determined it was set correctly. What's causing this issue?
My current codebase looks like this:
beforeEach(async () => {
const httpSpy = jasmine.createSpyObj('HttpClient', ['get']); //funcName for any function name that you need to exist on the spy objects
const dataSpy = jasmine.createSpyObj('GlobalDataService', ['funcName']);
await TestBed.configureTestingModule({
declarations: [ DocumentTreeviewComponent ],
imports: [ TreeviewModule.forRoot(), ReactiveFormsModule, MatProgressBarModule,
BrowserModule, MatProgressSpinnerModule, HttpClientTestingModule, MatDialogModule, MatInputModule
/* MatFormFieldModule, MatInputModule */ ],
providers: [ GlobalDataService, DatePipe,
{ provide: DocumentTreeService, useValue: mockDocumentTreeService },
{ provide: HttpClient, useValue: httpSpy },
{ provide: GlobalDataService, useValue: dataSpy }] // modify this line to provide a mock
})
.compileComponents();
// To access the service from your tests
documentTreeService = TestBed.inject(DocumentTreeService);
});
the document-treeview.component.ts:
import { Component, OnInit } from '@angular/core';
import { TreeviewItem, TreeviewConfig } from 'ngx-treeview';
import { GlobalDataService } from '../services/global-data.service';
import { DocumentTreeService } from '../services/DocumentTreeService';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-document-treeview',
templateUrl: './document-treeview.component.html',
styleUrls: ['./document-treeview.component.scss']
})
export class DocumentTreeviewComponent implements OnInit {
dropdownEnabled = false;
items: TreeviewItem[];
values: number[];
config = TreeviewConfig.create({
hasAllCheckBox: false,
hasFilter: true,
hasCollapseExpand: true,
maxHeight: 435
});
constructor(public service: DocumentTreeService, public DataService: GlobalDataService) {
this.getDocumentList(false);
}
public docAmount;
ngOnInit(): void {
setInterval(() => {
if (!this.items && this.service.treeviewmtextresponse) {
if (this.service.finalTreeviewElements.length !== 0) {
this.items = this.service.finalTreeviewElements;
} else {
this.items = this.service.getDocItems();
}
}
if (this.DataService.newDocumentAdded === true || this.DataService.documentDeleted === true) {
this.resetDocuments();
if (this.service.treeviewmtextresponse.length > this.docAmount && this.DataService.newDocumentAdded === true || this.service.treeviewmtextresponse.length < this.docAmount && this.DataService.documentDeleted === true) {
this.DataService.newDocumentAdded = false;
this.DataService.documentDeleted = false;
this.docAmount = this.service.treeviewmtextresponse.length;
}
}
}, 1000);
}
public resetDocuments() {
setTimeout(() => {
// reset treeview arrays
this.items = null;
this.service.docNames = [];
this.service.FolderItems = [];
this.service.treeviewItem = [];
this.service.finalTreeviewElements = [];
// reset treeviewmtextresponse
this.service.treeviewmtextresponse = null;
this.getDocumentList(true);
}, 1000);
}
onFilterChange(value: string): void {
console.log('filter:', value);
}
public getFolderName(event) {
// irrelevant for the unit test
}
openTonicUrl(event){
// this.openDialog(event);
if (this.DataService.SelectedDocumentName) {
window.open(`${environment.APIUrl}/text/gui/open/%5Chome%5Ctestproject%5C` + this.DataService.SelectedDocumentName, '_blank');
}
}
public getDocumentList(refresh: boolean) {
return this.service.http.get(this.DataService.getDocumentUrl, this.DataService.httpOptions)
.subscribe(
documentResponse => {
this.service.treeviewmtextresponse = documentResponse['soap:Envelope']['soap:Body'][
'ns2:getDocumentsResponse'
]['return'];
if (refresh === false) {
this.docAmount = this.service.treeviewmtextresponse.length;
}
this.DataService.documentListLoaded = true;
},
error => {
console.log('There was an error: ', error);
});
}
}
Upvotes: 1
Views: 8909
Reputation: 2895
A service needs to use @Injectable() decorator for the class.
e.g.
@Injectable({providedIn: 'root'}) //if you want to provide it in a certain module scope. Remove the `providedIn`
export class DocumentTreeService {...
If that doesn't fix it on it's own. The following is another way to inject services that have dependencies.
let documentTreeService: DocumentTreeService;
beforeEach(async () => {
for any function name that you need to exist on the spy objects
const dataSpy = jasmine.createSpyObj('GlobalDataService', ['funcName']);
await TestBed.configureTestingModule({
//... removed for brevity
imports: [HttpClientTestingModule, ...],
providers: [
DocumentTreeService,
{ provide: GlobalDataService, useValue: dataSpy }
]
})
.compileComponents();
//To access the service from your tests
documentTreeService = TestBed.inject(DocumentTreeService);
});
Upvotes: 4
Reputation: 2554
Since you are writing a unit test, you only want to test the component and not any of its dependencies. Optimally, you can Mock all other components declared, as well as all providers.
A good library for this is ng-mocks. Then, instead of being forced to check your service along with your component, you can just mock it.
providers: [ DocumentTreeService ]
can then be changed to
providers: [ {
provide: DocumentTreeService ,
useValue: MockService(DocumentTreeService)
}]
That way you can separate the external service from your component's unit test
Upvotes: 0
Reputation: 18849
Your DocumentTreeService
is using HttpClient
and GlobalDataService
and you're not providing them in the TestBed
module. I would mock the DocumentTreeService
and not provide the actual service when testing the component.
describe('DocumentTreeviewComponent', () => {
let component: DocumentTreeviewComponent;
let fixture: ComponentFixture<DocumentTreeviewComponent>;
let mockDocumentTreeService = jasmine.createSpyObj('documentTreeService', ['getDocItems']); // add this line, also look into how to mock external dependencies such as a service
// !! Remove the <DocumentTreeService> on the above line !!
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ DocumentTreeviewComponent ],
imports: [ TreeviewModule.forRoot(), ReactiveFormsModule, MatProgressBarModule,
BrowserModule, MatProgressSpinnerModule, /* MatFormFieldModule, MatInputModule */ ],
providers: [{ provide: DocumentTreeService, useValue: mockDocumentTreeService }] // modify this line to provide a mock
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(DocumentTreeviewComponent);
component = fixture.componentInstance;
mockDocumentTreeService.getDocItems().and.returnValue([]); // we have mocked getDocItems to return an empty array. You can return anything you want for that function.
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Upvotes: 3