Error: Can't resolve all parameters for (?, ?)

Error: Can't resolve all parameters for (?, ?)

我已经为我的 Angular 10 组件编写了以下单元测试,它基本上显示了具有一些交互性的树视图:

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

我的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;
    }
}

我的完整错误信息:

Error: Can't resolve all parameters for DocumentTreeService: (?, ?).

我最初以为我把服务放在正确的类别中搞砸了,但经过反复试验和一些逻辑我确定它设置正确。是什么导致了这个问题?


更新 1

我当前的代码库如下所示:

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

更新 2

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

您的 DocumentTreeService 使用 HttpClientGlobalDataService 而您没有在 TestBed 模块中提供它们。我会模拟 DocumentTreeService 并且在测试组件时不提供实际服务。

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

由于您正在编写单元测试,所以您只想测试组件而不是它的任何依赖项。最佳情况下,您可以 Mock 声明的所有其他组件以及所有提供程序。

一个很好的库是 ng-mocks。然后,您可以模拟它,而不是被迫检查您的服务和您的组件。

providers: [ DocumentTreeService ]

然后可以改成

providers: [ {
    provide: DocumentTreeService ,
    useValue: MockService(DocumentTreeService)
}]

这样您就可以将外部服务与组件的单元测试分开

服务需要为 class.

使用 @Injectable() 装饰器

例如

@Injectable({providedIn: 'root'}) //if you want to provide it in a certain module scope. Remove the `providedIn`
export class DocumentTreeService {...

如果这不能自行解决。下面是另一种注入具有依赖关系的服务的方法。

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