You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Angular组件单元测试报错:无法读取undefined的paramMap属性

Angular单元测试报错:TypeError: Cannot read properties of undefined (reading 'paramMap')

报错信息

TypeError: Cannot read properties of undefined (reading 'paramMap')

组件代码(product-list.component.ts)

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { CartItem } from 'src/app/common/cart-item';
import { Product } from 'src/app/common/product';
import { AuthService } from 'src/app/services/auth.service';
import { CartService } from 'src/app/services/cart.service';
import { ProductService } from 'src/app/services/product.service';

@Component({
  selector: 'app-product-list',
  templateUrl: './product-list.component.html',
  styleUrls: ['./product-list.component.css']
})
export class ProductListComponent implements OnInit {
  products: Product[] = [];
  currentCategoryId: number = 0;
  previousCategoryId: number = 1;
  currentCategoryName: String | null = null; 
  searchMode: boolean = false;

  pageNumber: number = 1;
  pageSize: number = 10;
  totalElements: number = 10;

  isAdmin: boolean = false;

  constructor(
    private productService: ProductService, 
    private route: ActivatedRoute,
    private cartService: CartService,
    private router: Router,
    private authService: AuthService
  ) { }

  ngOnInit(): void {    
    this.route.paramMap.subscribe(() => {
      this.productsList();
    });
    this.authService.isAdmin.subscribe((res) => {
      this.isAdmin = res;
    })
  }

  productsList() {
    this.searchMode = this.route.snapshot.paramMap.has("keyword");

    if (this.searchMode) {      
      this.handleSearchProductsList();
    } else {
      this.handleProductsList();
    }
  }

  handleSearchProductsList() {
    const keyword: string = this.route.snapshot.paramMap.get("keyword")!;
    this.productService.getSearchProducts(keyword).subscribe((res) => {
      this.products = res;
    });
  }

  handleProductsList() {
    const hasCategoryId: boolean = this.route.snapshot.paramMap.has("id");
    
    if (hasCategoryId) {
      this.currentCategoryId = +this.route.snapshot.paramMap.get("id")!;
      this.currentCategoryName = this.route.snapshot.paramMap.get("name");
      this.productService.getProductListByCategoryId(this.currentCategoryId).subscribe((res) => {
        this.products = res;
      })
    } else {
      this.productService.getProductList().subscribe((res) => {        
        this.products = res;
      })
    }
  }

  addToCart(product: Product) {
    this.cartService.addToCart(new CartItem(product))
  }

  deleteProduct(product: Product) {
    this.productService.deleteProduct(product.id).subscribe((res) => {
      if (res.success) {
        this.handleProductsList()
      }
    })
  }
}

修复后的测试代码(product-list.component.spec.ts)

import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap } from '@angular/router';
import { of, Subject } from 'rxjs';
import { Product } from 'src/app/common/product';
import { AuthService } from 'src/app/services/auth.service';
import { ProductService } from 'src/app/services/product.service';

import { ProductListComponent } from './product-list.component';

describe('ProductListComponent', () => {
  let PRODUCTS: Product[];
  let mockProductService: any;
  let mockAuthService: any;
  let mockActivatedRoute: any;
  let component: ProductListComponent;
  let fixture: ComponentFixture<ProductListComponent>;

  beforeEach(async () => {
    // 先初始化所有mock对象
    mockAuthService = jasmine.createSpyObj('AuthService', [], { isAdmin: new Subject() });
    mockProductService = jasmine.createSpyObj('ProductService', ['getProductList', 'getProductListByCategoryId', 'getSearchProducts', 'deleteProduct']);
    
    // 完整模拟ActivatedRoute的paramMap订阅和snapshot参数
    mockActivatedRoute = {
      paramMap: of(convertToParamMap({})),
      snapshot: {
        paramMap: convertToParamMap({})
      }
    };

    PRODUCTS = [
      {
        id: 1, 
        sku: "test1",
        name: "test1",
        description: "test1",
        unitPrice: 1,
        imageUrl: "test1",
        active: true,
        unitsInStock: 1,
        category: 1,
        dateCreated: new Date(),
        lastUpdated: new Date()
      },
      {
        id: 2, 
        sku: "test2",
        name: "test2",
        description: "test2",
        unitPrice: 2,
        imageUrl: "test2",
        active: true,
        unitsInStock: 2,
        category: 2,
        dateCreated: new Date(),
        lastUpdated: new Date()
      }
    ];

    // 配置TestBed,传入已初始化的mock对象
    await TestBed.configureTestingModule({
      declarations: [ ProductListComponent ],
      providers: [
        { provide: ProductService, useValue: mockProductService },
        { provide: ActivatedRoute, useValue: mockActivatedRoute },
        { provide: AuthService, useValue: mockAuthService }
      ]
    }).compileComponents();

    fixture = TestBed.createComponent(ProductListComponent);
    component = fixture.componentInstance;   

    mockAuthService.isAdmin.next(false);
    fixture.detectChanges();
  });

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

  it("should call handleProductsList after deleting product successfully", () => {
    spyOn(component, 'handleProductsList');
    component.products = PRODUCTS;
    mockProductService.deleteProduct.and.returnValue(
      of({ success: true, status: 200, message:"your product has been deleted" })
    );
    
    component.deleteProduct(PRODUCTS[0]);
    expect(component.handleProductsList).toHaveBeenCalled();
  });
});

核心修复要点

  1. 调整mock初始化顺序:原代码先配置TestBed再创建mock,导致注入的ActivatedRoute为undefined,必须先初始化mock再传入TestBed的providers。
  2. 完整模拟ActivatedRoute:组件同时使用了route.paramMap.subscribe()和route.snapshot.paramMap,需用convertToParamMap创建符合Angular规范的ParamMap对象,同时模拟两个属性。
  3. 补充必要的spy方法:添加getProductListByCategoryId和getSearchProducts的spy,避免组件调用时出现未定义错误;删除测试中需手动监听handleProductsList方法调用。

内容的提问来源于stack exchange,提问作者kyubei

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 05:01:36