Angular Karma - 组件未定义

Angular Karma - Component undefined

我正在尝试对需要 Resolver、Router 和 ActivatedRoute 作为依赖项的组件进行单元测试。我尝试使用 RouterTestingModule 并模拟我的解析器以在测试模块中提供它们,但它似乎对组件实例的创建有一些副作用。

这是我的组件的代码:

History.component.ts

import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { Subscription } from 'rxjs';
import { Transaction } from '../models/transaction.model';

@Component({
  selector: 'app-history',
  templateUrl: './history.component.html',
  styleUrls: ['./history.component.scss']
})
export class HistoryComponent implements OnInit, OnDestroy {
  history: Transaction[] = [];
  selectedTransaction: Transaction | undefined;
  subscription: Subscription = new Subscription();

  constructor(
    private route: ActivatedRoute,
    private router: Router,
  ) { }

  ngOnInit(): void {
    this.history = this.route.snapshot.data.history;
    const routeSubscription = this.route.params.subscribe((params) => {
      if (params.id) {
        this.setSelectedTransaction(+params.id);
      }
    });
    this.subscription.add(routeSubscription);
  }

  setSelectedTransaction(transactionId: number): void {
    const historyTransaction = this.history.find((transaction) => transaction.id === transactionId);
    this.selectedTransaction = historyTransaction;
  }

  displayTransaction(transaction: Transaction): void {
    this.router.navigate(['transactions', transaction.id]);
  }

  ngOnDestroy(): void {
    this.subscription.unsubscribe();
  }
}

这是当前的单元测试:

History.spec.ts

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, Resolve, Routes } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { Observable, of } from 'rxjs';
import { Transaction } from '../models/transaction.model';

import { HistoryComponent } from './history.component';
import { HistoryResolver } from './history.resolver';

const mockRawTransactions = [
  {
    "id":"1",
    "created_at":"2016-01-01T08:30:39-0300",
    "counterparty_name":"Uber",
    "debit":"false",
    "credit":"true",
    "amount":"44.20",
    "currency":"EUR",
    "operation_type":"refund",
    "attachements":[  
      {  
          "url":"https:\/\/fakeimg.pl\/350x200\/?text=Hello"
      }
    ],
  }
];
const mockTransactions = mockRawTransactions.map((transaction) => new Transaction(transaction));

class HistoryMockResolver implements Resolve<Transaction[]> {

  resolve(): Observable<Transaction[]> {
    return of(mockTransactions);
  }
}

describe('HistoryComponent', () => {
  const historyRoutes: Routes = [
    { path: 'transactions', component: HistoryComponent },
    { path: 'transactions/:id', component: HistoryComponent },
  ];
  let component: HistoryComponent;
  let fixture: ComponentFixture<HistoryComponent>;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ HistoryComponent ],
      imports: [
        RouterTestingModule.withRoutes(historyRoutes),
      ],
      providers: [
        {
          provide: ActivatedRoute,
          useValue: {
            snapshot: {
              params: { id: 1 },
            },
          },
        },
        { provide: HistoryResolver, useClass: HistoryMockResolver },
      ]
    })
    .compileComponents();
  });

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

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

  it('should display transactions', () => {
    component.history = mockTransactions;
    expect(component.history).toBeDefined();
    expect(component.history).toHaveSize(mockRawTransactions.length);
  });

  it('should display a single transaction', () => {
    component.history = mockTransactions;
    component.displayTransaction(component.history[0]);
    expect(component.selectedTransaction).toBeDefined();
    expect(component.selectedTransaction).toEqual(component.history[0]);
  });
});

组件已定义并在 console.log 中显示良好,而 运行 在 Karma 中的测试,但 Karma 会为每个测试用例引发错误并将组件评估为未定义。

这是第一个错误:

TypeError: Cannot read property 'history' of undefined
at HistoryComponent.ngOnInit (http://localhost:9876/_karma_webpack_/webpack:/src/app/history/history.component.ts:22:45)
at callHook (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:2486:1)
at callHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:2457:1)
at executeInitAndCheckHooks (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:2408:1)
at refreshView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:9207:1)
at renderComponentOrTemplate (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:9306:1)
at tickRootContext (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:10532:1)
at detectChangesInRootView (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:10557:1)
at RootViewRef.detectChanges (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/core.js:22569:1)
at ComponentFixture._tick (http://localhost:9876/_karma_webpack_/webpack:/node_modules/@angular/core/__ivy_ngcc__/fesm2015/testing.js:141:1)

我应该在测试模块中修复什么?

这是因为 this.route.snapshot.data.history 数据未定义,因为您没有在模拟激活快照中传递它。

您可以在 History.spec.ts

中更新您的提供商以获得已激活的路线快照
{
  provide: ActivatedRoute,
  useValue: {
    snapshot: {
      params: { id: 1 },
      data: {history: 'something-history-obj'}
    },
  },
},

或者您始终可以在 History.component.ts 中使用 this.route.snapshot.data?.history 如果它确实可以为 null