简体   繁体   中英

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

I am trying to write a unit test for my angular component and I am stuck at this error. I have been debugging for a long time with no luck. Any help is appreciated... I have created the mockActivatedRoute in spec.ts but it keeps showing the below error. How come the paramMap property cannot be read? Here is the error message I get:

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

Below is my code:

.ts file

import { Component, OnChanges, 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() {
    // check if there is id in activatedroute 
    const hasCategoryId: boolean = this.route.snapshot.paramMap.has("id");
    
    if (hasCategoryId) {
      // get the id from the route + is required since the get return a string
      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) {
    // subscribe the cart servie add to cart 
    this.cartService.addToCart(new CartItem(product))
  }

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

}

.ts.spec

import { HttpClient, HttpHandler } from '@angular/common/http';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { ActivatedRoute, convertToParamMap, RouteConfigLoadEnd } from '@angular/router';
import { async, Observable, 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 () => {
    await TestBed.configureTestingModule({
      declarations: [ ProductListComponent ],
      providers: [
        { provide: ProductService, useValue: mockProductService },
        { provide: ActivatedRoute, useValue: mockActivatedRoute },
        { provide: AuthService, useValue: mockAuthService }
    ]

    })
    .compileComponents();
    
    mockAuthService = jasmine.createSpyObj({}, { isAdmin: new Subject() })
    mockProductService = jasmine.createSpyObj(['getProductList', 'getProducts', 'deleteProduct'])
    mockActivatedRoute = jasmine.createSpyObj([paramMap: new Observable()], {params: { id: 1 }})

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

    fixture = TestBed.createComponent(ProductListComponent);
    component = fixture.componentInstance;   
    
    mockAuthService.isAdmin.next(false);
    mockActivatedRoute.paramMap.and.returnValue(of([]) )

    fixture.detectChanges();
  });

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

    
it("should delete the selected product from products", () => {
    component.products = PRODUCTS;
    mockProductService.deleteProduct.and.returnValue(
            of({ success: true, status: 200, message:"you product has been deleted" }));
    
    component.deleteProduct(PRODUCTS[0]);
    expect(component.handleProductsList).toHaveBeenCalled();
})

});

I have tried to search online for solutions on this issue with no luck. I am expecting for this unit test to work but I am unsure on how to fix the issue.

It seems that you are defining your mock after you've declared the provider. So Angular injects undefined for ActivatedRoute . Here's a distilled version of your problem:

describe('ProductListComponent', () => {
  // 1. you declare `mockActivatedRoute`; its value is `undefined` at this point
  let mockActivatedRoute: any;

  beforeEach(async () => {
    await TestBed.configureTestingModule({
      declarations: [ ProductListComponent ],
      providers: [
        // 2. you define a custom provider for `ActivatedRoute`;
        // this provider uses a value stored in `mockActivatedRoute`,
        // which is still `undefined` at this point
        { provide: ActivatedRoute, useValue: mockActivatedRoute },
      ]
    })
    .compileComponents();
    
    // 3. you assign a value to `mockActivatedRoute`;
    // unfortunately it is too late - the provider has already been defined...
    mockActivatedRoute = jasmine.createSpyObj([paramMap: new Observable()], {params: { id: 1 }})

To fix it make sure to assign correct values to all your mocks before the providers are declared:

beforeEach(async () => {
    mockAuthService = jasmine.createSpyObj({}, { isAdmin: new Subject() })
    mockProductService = jasmine.createSpyObj(['getProductList', 'getProducts', 'deleteProduct'])
    mockActivatedRoute = jasmine.createSpyObj([paramMap: new Observable()], {params: { id: 1 }})

    await TestBed.configureTestingModule({
      declarations: [ ProductListComponent ],
      providers: [
        { provide: ProductService, useValue: mockProductService },
        { provide: ActivatedRoute, useValue: mockActivatedRoute },
        { provide: AuthService, useValue: mockAuthService }
      ]
    })
    .compileComponents();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM